Skip to main content

tsz_emitter/emitter/
mod.rs

1//! Emitter - Emitter using `NodeArena`
2//!
3//! This emitter uses the Node architecture for cache-optimized AST access.
4//! It works directly with `NodeArena` instead of the old Node enum.
5//!
6//! # Architecture
7//!
8//! - Uses `NodeArena` for AST access (16-byte nodes, 13x cache improvement)
9//! - Dispatches based on Node.kind (u16)
10//! - Uses accessor methods to get typed node data
11//!
12//! # Module Organization
13//!
14//! The emitter is organized as a directory module:
15//! - `mod.rs` - Core Printer struct, dispatch logic, and emit methods
16//! - `expressions.rs` - Expression emission helpers
17//! - `statements.rs` - Statement emission helpers
18//! - `declarations.rs` - Declaration emission helpers
19//! - `functions.rs` - Function emission helpers
20//! - `types.rs` - Type emission helpers
21//! - `jsx.rs` - JSX emission helpers
22//!
23//! Note: pub(super) fields and methods allow future submodules to access Printer internals.
24
25use crate::context::emit::EmitContext;
26use crate::context::transform::{IdentifierId, TransformContext, TransformDirective};
27use crate::output::source_writer::{SourcePosition, SourceWriter, source_position_from_offset};
28use crate::transforms::{ClassES5Emitter, EnumES5Emitter, NamespaceES5Emitter};
29use rustc_hash::{FxHashMap, FxHashSet};
30use std::collections::VecDeque;
31use std::sync::Arc;
32use tracing::{debug, warn};
33use tsz_parser::parser::NodeIndex;
34use tsz_parser::parser::node::{Node, NodeArena};
35use tsz_parser::parser::syntax_kind_ext;
36use tsz_scanner::SyntaxKind;
37
38mod binding_patterns;
39mod comment_helpers;
40mod comments;
41mod declarations;
42mod declarations_class;
43mod declarations_class_members;
44mod declarations_namespace;
45mod es5;
46mod expressions;
47mod expressions_literals;
48mod functions;
49mod helpers;
50mod jsx;
51mod literals;
52mod module_emission;
53mod module_emission_exports;
54mod module_emission_imports;
55mod module_wrapper;
56mod source_file;
57mod special_expressions;
58mod statements;
59mod template_literals;
60mod transform_dispatch;
61pub mod type_printer;
62mod types;
63
64pub use comments::{
65    CommentKind, CommentRange, get_leading_comment_ranges, get_trailing_comment_ranges,
66};
67
68// Re-export common types for backward compatibility
69pub use tsz_common::common::{ModuleKind, NewLineKind, ScriptTarget};
70
71// =============================================================================
72// Emitter Options
73// =============================================================================
74
75/// Printer configuration options.
76#[derive(Clone, Debug)]
77pub struct PrinterOptions {
78    /// Remove comments from output
79    pub remove_comments: bool,
80    /// Target ECMAScript version
81    pub target: ScriptTarget,
82    /// Use single quotes for strings
83    pub single_quote: bool,
84    /// Omit trailing semicolons
85    pub omit_trailing_semicolon: bool,
86    /// Don't emit helpers
87    pub no_emit_helpers: bool,
88    /// Module kind
89    pub module: ModuleKind,
90    /// New line character
91    pub new_line: NewLineKind,
92    /// Downlevel iteration (for-of with full iterator protocol)
93    pub downlevel_iteration: bool,
94    /// Set of import specifier nodes that should be elided (type-only imports)
95    pub type_only_nodes: Arc<FxHashSet<NodeIndex>>,
96    /// Emit "use strict" for every source file
97    pub always_strict: bool,
98    /// Emit class fields using Object.defineProperty semantics when downleveling
99    pub use_define_for_class_fields: bool,
100    /// Enable legacy (experimental) decorator lowering (`__decorate` style)
101    pub legacy_decorators: bool,
102    /// Emit interop helpers (`__importStar`, `__importDefault`) for CJS/ESM interop
103    pub es_module_interop: bool,
104    /// When true, treat all non-declaration files as modules (moduleDetection=force)
105    pub module_detection_force: bool,
106}
107
108impl Default for PrinterOptions {
109    fn default() -> Self {
110        Self {
111            remove_comments: false,
112            target: ScriptTarget::ESNext,
113            single_quote: false,
114            omit_trailing_semicolon: false,
115            no_emit_helpers: false,
116            module: ModuleKind::None,
117            new_line: NewLineKind::LineFeed,
118            downlevel_iteration: false,
119            type_only_nodes: Arc::new(FxHashSet::default()),
120            always_strict: false,
121            use_define_for_class_fields: false,
122            legacy_decorators: false,
123            es_module_interop: false,
124            module_detection_force: false,
125        }
126    }
127}
128
129#[derive(Default)]
130struct ParamTransformPlan {
131    params: Vec<ParamTransform>,
132    rest: Option<RestParamTransform>,
133}
134
135#[derive(Default)]
136struct TempScopeState {
137    temp_var_counter: u32,
138    generated_temp_names: FxHashSet<String>,
139    first_for_of_emitted: bool,
140    preallocated_temp_names: VecDeque<String>,
141    preallocated_assignment_temps: VecDeque<String>,
142    preallocated_logical_assignment_value_temps: VecDeque<String>,
143    hoisted_assignment_value_temps: Vec<String>,
144    hoisted_assignment_temps: Vec<String>,
145}
146
147impl ParamTransformPlan {
148    const fn has_transforms(&self) -> bool {
149        !self.params.is_empty() || self.rest.is_some()
150    }
151}
152
153struct ParamTransform {
154    name: String,
155    pattern: Option<NodeIndex>,
156    initializer: Option<NodeIndex>,
157}
158
159struct RestParamTransform {
160    name: String,
161    pattern: Option<NodeIndex>,
162    index: usize,
163}
164
165struct TemplateParts {
166    cooked: Vec<String>,
167    raw: Vec<String>,
168    expressions: Vec<NodeIndex>,
169}
170
171// =============================================================================
172// Printer
173// =============================================================================
174
175/// Maximum recursion depth for emit to prevent infinite loops
176const MAX_EMIT_RECURSION_DEPTH: u32 = 1000;
177
178/// Printer that works with `NodeArena`.
179///
180/// Uses `SourceWriter` for output generation (enables source map support).
181/// Uses `EmitContext` for transform-specific state management.
182/// Uses `TransformContext` for directive-based transforms (Phase 2 architecture).
183pub struct Printer<'a> {
184    /// The `NodeArena` containing the AST.
185    pub(super) arena: &'a NodeArena,
186
187    /// Source writer for output generation and source map tracking
188    pub(super) writer: SourceWriter,
189
190    /// Emit context holding options and transform state
191    pub(super) ctx: EmitContext,
192
193    /// Transform directives from lowering pass (optional, defaults to empty)
194    pub(super) transforms: TransformContext,
195
196    /// Emit `void 0` for missing initializers during recovery.
197    pub(super) emit_missing_initializer_as_void_0: bool,
198
199    /// Source text for detecting single-line constructs
200    pub(super) source_text: Option<&'a str>,
201
202    /// Source text for source map generation (kept separate from comment emission).
203    pub(super) source_map_text: Option<&'a str>,
204
205    /// Pending source position for mapping the next write.
206    pub(super) pending_source_pos: Option<SourcePosition>,
207
208    /// Recursion depth counter to prevent infinite loops
209    emit_recursion_depth: u32,
210
211    /// All comments in the source file, collected once during `emit_source_file`.
212    /// Used for distributing comments to blocks and other nested constructs.
213    pub(super) all_comments: Vec<tsz_common::comments::CommentRange>,
214
215    /// Shared index into `all_comments`, monotonically advancing as comments are emitted.
216    /// Used across `emit_source_file` and `emit_block` to prevent double-emission.
217    pub(super) comment_emit_idx: usize,
218
219    /// All identifier texts in the source file.
220    /// Collected once at `emit_source_file` start for temp name collision detection.
221    /// Mirrors TypeScript's `sourceFile.identifiers` used by `makeUniqueName`.
222    pub(super) file_identifiers: FxHashSet<String>,
223
224    /// Set of generated temp names (_a, _b, etc.) to avoid collisions.
225    /// Tracks ALL generated temp names across destructuring and for-of lowering.
226    pub(super) generated_temp_names: FxHashSet<String>,
227
228    /// Stack for saving/restoring temp naming state when entering function scopes.
229    temp_scope_stack: Vec<TempScopeState>,
230
231    /// Whether the first for-of loop has been emitted (uses special `_i` index name).
232    pub(super) first_for_of_emitted: bool,
233
234    /// Whether we're inside a namespace IIFE (strip export/default modifiers from classes).
235    pub(super) in_namespace_iife: bool,
236
237    /// When set, the next enum emit should fold the namespace export into the IIFE closing.
238    /// E.g., `(Color = A.Color || (A.Color = {}))` instead of `(Color || (Color = {}))`.
239    pub(super) enum_namespace_export: Option<String>,
240
241    /// Set to true when the next `MODULE_DECLARATION` emit should use parent namespace
242    /// assignment in its IIFE closing. This is set by `emit_namespace_body_statements`
243    /// when the module is wrapped in an `EXPORT_DECLARATION`.
244    pub(super) namespace_export_inner: bool,
245
246    /// Marker that the next block emission is a function body.
247    pub(super) emitting_function_body_block: bool,
248
249    /// The name of the current namespace we're emitting inside (if any).
250    /// Used for nested exported namespaces to emit proper IIFE parameters.
251    pub(super) current_namespace_name: Option<String>,
252
253    /// Override name for anonymous default exports (e.g., "`default_1`").
254    /// When set, class/function emitters use this instead of leaving the name blank.
255    pub(super) anonymous_default_export_name: Option<String>,
256    /// For CommonJS class exports, emit `exports.X = X;` immediately after class
257    /// declaration and before post-class lowered statements (static fields/blocks).
258    pub(super) pending_commonjs_class_export_name: Option<String>,
259
260    /// Names of namespaces already declared with `var name;` to avoid duplicates.
261    pub(super) declared_namespace_names: FxHashSet<String>,
262
263    /// Exported variable/function/class names in the current namespace IIFE.
264    /// Used to qualify identifier references: `foo` → `ns.foo`.
265    pub(super) namespace_exported_names: FxHashSet<String>,
266
267    /// When true, suppress namespace identifier qualification (emitting a declaration name).
268    pub(super) suppress_ns_qualification: bool,
269
270    /// When true, do not substitute CommonJS named imports while emitting identifiers.
271    /// Used for property-name positions like `obj.name`.
272    pub(super) suppress_commonjs_named_import_substitution: bool,
273
274    /// Pending class field initializers to inject into constructor body.
275    /// Each entry is (`field_name`, `initializer_node_index`).
276    pub(super) pending_class_field_inits: Vec<(String, NodeIndex)>,
277
278    /// Pending auto-accessor field initializers to emit in constructor body.
279    /// Each tuple is (`weakmap_storage_name`, `initializer_expression`).
280    /// `initializer_expression` is `None` when the accessor field has no
281    /// initializer and should default to `void 0`.
282    pub(super) pending_auto_accessor_inits: Vec<(String, Option<NodeIndex>)>,
283
284    /// Temp names for assignment target values that need to be hoisted as `var _a, _b, ...;`.
285    /// These are emitted on a separate declaration list before reference temps.
286    pub(super) hoisted_assignment_value_temps: Vec<String>,
287
288    /// Temp names for assignment target values that must be reserved before references.
289    /// These are used by `make_unique_name_hoisted_value`.
290    pub(super) preallocated_logical_assignment_value_temps: VecDeque<String>,
291
292    /// Temp names for assignment target values that must be reserved before references.
293    /// These are used by `make_unique_name_hoisted_assignment`.
294    pub(super) preallocated_assignment_temps: VecDeque<String>,
295
296    /// Temp variable names that need to be hoisted to the top of the current scope
297    /// as `var _a, _b, ...;`. Used for assignment targets in helper expressions.
298    pub(super) hoisted_assignment_temps: Vec<String>,
299
300    /// Temp names reserved ahead-of-time and consumed before generating new names.
301    pub(super) preallocated_temp_names: VecDeque<String>,
302
303    /// Temp names for ES5 iterator-based for-of lowering that must be emitted
304    /// as top-level `var` declarations (e.g., `e_1, _a, e_2, _b`).
305    pub(super) hoisted_for_of_temps: Vec<String>,
306
307    /// CommonJS named import substitutions (e.g. `f` -> `demoModule_1.f`).
308    /// Used to match tsc emit where named imports are referenced via module temps.
309    pub(super) commonjs_named_import_substitutions: FxHashMap<String, String>,
310
311    /// Pre-allocated return-temp names for iterator for-of nodes.
312    /// This lets nested loops reserve their return temp before outer loop
313    /// iterator/result temps, matching tsc temp ordering.
314    pub(super) reserved_iterator_return_temps: FxHashMap<NodeIndex, String>,
315
316    /// Current nesting depth for iterator for-of emission.
317    pub(super) iterator_for_of_depth: usize,
318
319    /// Current nesting depth for destructuring emission that should wrap spread inputs with `__read`.
320    pub(super) destructuring_read_depth: u32,
321}
322
323impl<'a> Printer<'a> {
324    const DEFAULT_OUTPUT_CAPACITY: usize = 1024;
325
326    fn estimate_output_capacity(source_len: usize) -> usize {
327        // Emit output can be slightly smaller (type erasure) or significantly larger
328        // (downlevel transforms/helpers). Bias toward ~1.5x while keeping a sane floor.
329        source_len
330            .saturating_mul(3)
331            .saturating_div(2)
332            .max(Self::DEFAULT_OUTPUT_CAPACITY)
333    }
334
335    /// Create a new Printer.
336    pub fn new(arena: &'a NodeArena) -> Self {
337        Self::with_options(arena, PrinterOptions::default())
338    }
339
340    /// Create a new Printer with options and source-length-informed preallocation.
341    pub fn with_source_text_len_and_options(
342        arena: &'a NodeArena,
343        source_text_len: usize,
344        options: PrinterOptions,
345    ) -> Self {
346        let capacity = Self::estimate_output_capacity(source_text_len);
347        Self::with_capacity_and_options(arena, capacity, options)
348    }
349
350    /// Create a new Printer with source-length-informed preallocation.
351    pub fn with_source_text_len(arena: &'a NodeArena, source_text_len: usize) -> Self {
352        Self::with_source_text_len_and_options(arena, source_text_len, PrinterOptions::default())
353    }
354
355    /// Create a new Printer with options and root-node-informed preallocation.
356    pub fn with_root_and_options(
357        arena: &'a NodeArena,
358        root: NodeIndex,
359        options: PrinterOptions,
360    ) -> Self {
361        let source_text_len = arena
362            .get(root)
363            .and_then(|node| arena.get_source_file(node))
364            .map_or(0, |source| source.text.len());
365        Self::with_source_text_len_and_options(arena, source_text_len, options)
366    }
367
368    /// Create a new Printer with root-node-informed preallocation.
369    pub fn with_root(arena: &'a NodeArena, root: NodeIndex) -> Self {
370        Self::with_root_and_options(arena, root, PrinterOptions::default())
371    }
372
373    /// Create a new Printer with pre-allocated output capacity
374    /// This reduces allocations when the expected output size is known (e.g., ~1.5x source size)
375    pub fn with_capacity(arena: &'a NodeArena, capacity: usize) -> Self {
376        Self::with_capacity_and_options(arena, capacity, PrinterOptions::default())
377    }
378
379    /// Create a new Printer with options.
380    pub fn with_options(arena: &'a NodeArena, options: PrinterOptions) -> Self {
381        Self::with_capacity_and_options(arena, Self::DEFAULT_OUTPUT_CAPACITY, options)
382    }
383
384    /// Create a new Printer with pre-allocated capacity and options.
385    pub fn with_capacity_and_options(
386        arena: &'a NodeArena,
387        capacity: usize,
388        options: PrinterOptions,
389    ) -> Self {
390        let mut writer = SourceWriter::with_capacity(capacity);
391        writer.set_new_line_kind(options.new_line);
392
393        // Create EmitContext from options (target controls ES5 vs ESNext)
394        let ctx = EmitContext::with_options(options);
395
396        Printer {
397            arena,
398            writer,
399            ctx,
400            transforms: TransformContext::new(), // Empty by default, can be set later
401            emit_missing_initializer_as_void_0: false,
402            source_text: None,
403            source_map_text: None,
404            pending_source_pos: None,
405            emit_recursion_depth: 0,
406            all_comments: Vec::new(),
407            comment_emit_idx: 0,
408            file_identifiers: FxHashSet::default(),
409            generated_temp_names: FxHashSet::default(),
410            temp_scope_stack: Vec::new(),
411            first_for_of_emitted: false,
412            in_namespace_iife: false,
413            enum_namespace_export: None,
414            namespace_export_inner: false,
415            emitting_function_body_block: false,
416            current_namespace_name: None,
417            anonymous_default_export_name: None,
418            pending_commonjs_class_export_name: None,
419            declared_namespace_names: FxHashSet::default(),
420            namespace_exported_names: FxHashSet::default(),
421            suppress_ns_qualification: false,
422            suppress_commonjs_named_import_substitution: false,
423            pending_class_field_inits: Vec::new(),
424            pending_auto_accessor_inits: Vec::new(),
425            hoisted_assignment_value_temps: Vec::new(),
426            preallocated_logical_assignment_value_temps: VecDeque::new(),
427            preallocated_assignment_temps: VecDeque::new(),
428            hoisted_assignment_temps: Vec::new(),
429            preallocated_temp_names: VecDeque::new(),
430            hoisted_for_of_temps: Vec::new(),
431            commonjs_named_import_substitutions: FxHashMap::default(),
432            reserved_iterator_return_temps: FxHashMap::default(),
433            iterator_for_of_depth: 0,
434            destructuring_read_depth: 0,
435        }
436    }
437
438    /// Create a new Printer with transform directives.
439    /// This is the Phase 2 constructor that accepts pre-computed transforms.
440    pub fn with_transforms(arena: &'a NodeArena, transforms: TransformContext) -> Self {
441        let mut printer = Self::new(arena);
442        printer.transforms = transforms;
443        printer
444    }
445
446    /// Create a new Printer with transforms and options.
447    pub fn with_transforms_and_options(
448        arena: &'a NodeArena,
449        transforms: TransformContext,
450        options: PrinterOptions,
451    ) -> Self {
452        let mut printer = Self::with_options(arena, options);
453        printer.transforms = transforms;
454        printer
455    }
456
457    /// Set whether to target ES5 behavior.
458    ///
459    /// This updates both the legacy `target_es5` bool and all derived
460    /// per-version lowering gates in the shared context.
461    pub const fn set_target_es5(&mut self, es5: bool) {
462        self.ctx.set_target_es5(es5);
463    }
464
465    /// Set the full script target.
466    ///
467    /// This keeps all derived feature gates synchronized, including `target_es5`.
468    pub const fn set_target(&mut self, target: ScriptTarget) {
469        self.ctx.set_target(target);
470    }
471
472    /// Set the module kind (`CommonJS`, ESM, etc.).
473    pub const fn set_module_kind(&mut self, kind: ModuleKind) {
474        self.ctx.options.module = kind;
475    }
476
477    /// Set auto-detect module mode. When enabled, the emitter will detect if
478    /// the source file contains import/export statements and apply `CommonJS`
479    /// transforms automatically.
480    pub const fn set_auto_detect_module(&mut self, enabled: bool) {
481        self.ctx.auto_detect_module = enabled;
482    }
483
484    /// Set the source text (for detecting single-line constructs).
485    pub fn set_source_text(&mut self, text: &'a str) {
486        self.source_text = Some(text);
487        let estimated = Self::estimate_output_capacity(text.len());
488        self.writer.ensure_output_capacity(estimated);
489    }
490
491    /// Enable declaration emit mode for `.d.ts` output.
492    ///
493    /// Declaration mode changes emission behavior in multiple nodes, such as:
494    /// - Skipping JS-only constructs
495    /// - Emitting `declare` signatures instead of values
496    /// - Keeping type-only information
497    pub const fn set_declaration_emit(&mut self, enabled: bool) {
498        self.ctx.flags.in_declaration_emit = enabled;
499    }
500
501    /// Set source text for source map generation without enabling comment emission.
502    pub const fn set_source_map_text(&mut self, text: &'a str) {
503        self.source_map_text = Some(text);
504    }
505
506    /// Enable source map generation and register the current source file.
507    pub fn enable_source_map(&mut self, output_name: &str, source_name: &str) {
508        self.writer.enable_source_map(output_name.to_string());
509        let content = self
510            .source_text_for_map()
511            .map(std::string::ToString::to_string);
512        self.writer.add_source(source_name.to_string(), content);
513    }
514
515    /// Generate source map JSON (if enabled).
516    pub fn generate_source_map_json(&mut self) -> Option<String> {
517        self.writer.generate_source_map_json()
518    }
519
520    fn source_text_for_map(&self) -> Option<&'a str> {
521        self.source_map_text.or(self.source_text)
522    }
523
524    fn queue_source_mapping(&mut self, node: &Node) {
525        if !self.writer.has_source_map() {
526            self.pending_source_pos = None;
527            return;
528        }
529
530        let Some(text) = self.source_text_for_map() else {
531            self.pending_source_pos = None;
532            return;
533        };
534
535        self.pending_source_pos = Some(source_position_from_offset(text, node.pos));
536    }
537
538    /// Check if a node spans a single line in the source.
539    /// Finds the first `{` and last `}` within the node's source span and checks
540    /// if there's a newline between them. Uses depth counting to handle nested braces correctly.
541    fn is_single_line(&self, node: &Node) -> bool {
542        if let Some(text) = self.source_text {
543            let actual_start = self.skip_trivia_forward(node.pos, node.end) as usize;
544            // Use actual token end, not node.end which includes trailing trivia.
545            // For example, `{ return x; }\n` has trailing newline in node.end,
546            // but we want to check only `{ return x; }`.
547            let token_end = self.find_token_end_before_trivia(node.pos, node.end);
548            let end = std::cmp::min(token_end as usize, text.len());
549            if actual_start < end {
550                let slice = &text[actual_start..end];
551                // Find the first `{` and its matching `}` using depth counting
552                // to handle nested braces (e.g., `{ return new Line({ x: 0 }, p); }`)
553                if let Some(open) = slice.find('{') {
554                    let mut depth = 1;
555                    let mut close = None;
556                    for (i, ch) in slice[open + 1..].char_indices() {
557                        match ch {
558                            '{' => depth += 1,
559                            '}' => {
560                                depth -= 1;
561                                if depth == 0 {
562                                    close = Some(open + 1 + i);
563                                    break;
564                                }
565                            }
566                            _ => {}
567                        }
568                    }
569                    if let Some(close) = close {
570                        let inner = &slice[open..close + 1];
571                        return !inner.contains('\n');
572                    }
573                }
574                return !slice.contains('\n');
575            }
576        }
577        false
578    }
579
580    /// Check if two nodes are on the same line in the source.
581    fn are_on_same_line_in_source(
582        &self,
583        node1: tsz_parser::parser::NodeIndex,
584        node2: tsz_parser::parser::NodeIndex,
585    ) -> bool {
586        if let Some(text) = self.source_text
587            && let (Some(n1), Some(n2)) = (self.arena.get(node1), self.arena.get(node2))
588        {
589            let start = std::cmp::min(n1.end as usize, text.len());
590            let end = std::cmp::min(n2.pos as usize, text.len());
591            if start < end {
592                // Check if there's a newline between the two nodes
593                return !text[start..end].contains('\n');
594            }
595        }
596        false
597    }
598
599    /// Get the output.
600    pub fn get_output(&self) -> &str {
601        self.writer.get_output()
602    }
603
604    /// Take the output.
605    pub fn take_output(self) -> String {
606        self.writer.take_output()
607    }
608    // =========================================================================
609    // Main Emit Method
610    // =========================================================================
611
612    /// Emit a node by index.
613    pub fn emit(&mut self, idx: NodeIndex) {
614        if idx.is_none() {
615            return;
616        }
617
618        let Some(node) = self.arena.get(idx) else {
619            return;
620        };
621
622        if let Some(source) = self.arena.get_source_file(node)
623            && self.transforms.is_empty()
624        {
625            let format = match self.ctx.options.module {
626                ModuleKind::AMD => Some(crate::context::transform::ModuleFormat::AMD),
627                ModuleKind::UMD => Some(crate::context::transform::ModuleFormat::UMD),
628                ModuleKind::System => Some(crate::context::transform::ModuleFormat::System),
629                _ => None,
630            };
631            if let Some(format) = format
632                && self.file_is_module(&source.statements)
633            {
634                let dependencies = self.collect_module_dependencies(&source.statements.nodes);
635                self.emit_module_wrapper(format, &dependencies, node, source, idx);
636                return;
637            }
638        }
639
640        self.emit_node(node, idx);
641    }
642
643    /// Emit a node in an expression context.
644    /// If the node is an error/unknown node, emits `void 0` for parse error tolerance.
645    pub fn emit_expression(&mut self, idx: NodeIndex) {
646        if idx.is_none() {
647            self.write("void 0");
648            return;
649        }
650
651        let Some(node) = self.arena.get(idx) else {
652            self.write("void 0");
653            return;
654        };
655
656        // Check if this is an error/unknown node
657        use tsz_scanner::SyntaxKind;
658        if node.kind == SyntaxKind::Unknown as u16 {
659            self.write("void 0");
660            return;
661        }
662
663        // Otherwise, emit normally
664        self.emit_node(node, idx);
665    }
666
667    /// Emit a node.
668    fn emit_node(&mut self, node: &Node, idx: NodeIndex) {
669        // Recursion depth check to prevent infinite loops
670        self.emit_recursion_depth += 1;
671        if self.emit_recursion_depth > MAX_EMIT_RECURSION_DEPTH {
672            // Log a warning about the recursion limit being exceeded.
673            // This helps developers identify problematic deeply nested ASTs.
674            warn!(
675                depth = MAX_EMIT_RECURSION_DEPTH,
676                node_kind = node.kind,
677                node_pos = node.pos,
678                "Emit recursion limit exceeded"
679            );
680            self.write("/* emit recursion limit exceeded */");
681            self.emit_recursion_depth -= 1;
682            return;
683        }
684
685        // Check transform directives first
686        let has_transform = !self.transforms.is_empty()
687            && Self::kind_may_have_transform(node.kind)
688            && self.transforms.has_transform(idx);
689        let previous_pending = self.pending_source_pos;
690
691        self.queue_source_mapping(node);
692        if has_transform {
693            self.apply_transform(node, idx);
694        } else {
695            let kind = node.kind;
696            self.emit_node_by_kind(node, idx, kind);
697        }
698
699        self.pending_source_pos = previous_pending;
700        self.emit_recursion_depth -= 1;
701    }
702
703    const fn kind_may_have_transform(kind: u16) -> bool {
704        matches!(
705            kind,
706            k if k == syntax_kind_ext::SOURCE_FILE
707                || k == syntax_kind_ext::CLASS_DECLARATION
708                || k == syntax_kind_ext::CLASS_EXPRESSION
709                || k == syntax_kind_ext::MODULE_DECLARATION
710                || k == syntax_kind_ext::ENUM_DECLARATION
711                || k == syntax_kind_ext::FUNCTION_DECLARATION
712                || k == syntax_kind_ext::FUNCTION_EXPRESSION
713                || k == syntax_kind_ext::ARROW_FUNCTION
714                || k == syntax_kind_ext::VARIABLE_STATEMENT
715                || k == syntax_kind_ext::VARIABLE_DECLARATION_LIST
716                || k == syntax_kind_ext::FOR_OF_STATEMENT
717                || k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION
718                || k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION
719                || k == syntax_kind_ext::TAGGED_TEMPLATE_EXPRESSION
720                || k == syntax_kind_ext::TEMPLATE_EXPRESSION
721                || k == syntax_kind_ext::CALL_EXPRESSION
722                || k == SyntaxKind::NoSubstitutionTemplateLiteral as u16
723        )
724    }
725
726    /// Emit a node by kind using default logic (no transforms).
727    /// This is the main dispatch method for emission.
728    fn emit_node_by_kind(&mut self, node: &Node, idx: NodeIndex, kind: u16) {
729        match kind {
730            // Identifiers
731            k if k == SyntaxKind::Identifier as u16 => {
732                // Check for substitution directives on identifier nodes.
733                if self.transforms.has_transform(idx) {
734                    if let Some(directive) = self.transforms.get(idx) {
735                        match directive {
736                            TransformDirective::SubstituteArguments => self.write("arguments"),
737                            TransformDirective::SubstituteThis { capture_name } => {
738                                let name = std::sync::Arc::clone(capture_name);
739                                self.write(&name);
740                            }
741                            _ => self.emit_identifier(node),
742                        }
743                    } else {
744                        self.emit_identifier(node);
745                    }
746                } else {
747                    self.emit_identifier(node);
748                }
749            }
750            k if k == SyntaxKind::PrivateIdentifier as u16 => {
751                // Private identifiers (#name) are emitted as-is for ES2022+ targets.
752                // For ES5/ES2015 targets, they should be lowered by the class transform.
753                if let Some(ident) = self.arena.get_identifier(node) {
754                    self.write(&ident.escaped_text);
755                }
756            }
757            k if k == syntax_kind_ext::TYPE_PARAMETER => {
758                self.emit_type_parameter(node);
759            }
760
761            // Literals
762            k if k == SyntaxKind::NumericLiteral as u16 => {
763                self.emit_numeric_literal(node);
764            }
765            k if k == SyntaxKind::BigIntLiteral as u16 => {
766                self.emit_bigint_literal(node);
767            }
768            k if k == SyntaxKind::StringLiteral as u16 => {
769                self.emit_string_literal(node);
770            }
771            k if k == SyntaxKind::RegularExpressionLiteral as u16 => {
772                self.emit_regex_literal(node);
773            }
774            k if k == SyntaxKind::TrueKeyword as u16 => {
775                self.write("true");
776            }
777            k if k == SyntaxKind::FalseKeyword as u16 => {
778                self.write("false");
779            }
780            k if k == SyntaxKind::NullKeyword as u16 => {
781                self.write("null");
782            }
783
784            // Binary expression
785            k if k == syntax_kind_ext::BINARY_EXPRESSION => {
786                self.emit_binary_expression(node);
787            }
788
789            // Unary expressions
790            k if k == syntax_kind_ext::PREFIX_UNARY_EXPRESSION => {
791                self.emit_prefix_unary(node);
792            }
793            k if k == syntax_kind_ext::POSTFIX_UNARY_EXPRESSION => {
794                self.emit_postfix_unary(node);
795            }
796
797            // Call expression
798            k if k == syntax_kind_ext::CALL_EXPRESSION => {
799                self.emit_call_expression(node);
800            }
801
802            // New expression
803            k if k == syntax_kind_ext::NEW_EXPRESSION => {
804                self.emit_new_expression(node);
805            }
806
807            // Property access
808            k if k == syntax_kind_ext::PROPERTY_ACCESS_EXPRESSION => {
809                self.emit_property_access(node);
810            }
811
812            // Element access
813            k if k == syntax_kind_ext::ELEMENT_ACCESS_EXPRESSION => {
814                self.emit_element_access(node);
815            }
816
817            // Parenthesized expression
818            k if k == syntax_kind_ext::PARENTHESIZED_EXPRESSION => {
819                self.emit_parenthesized(node);
820            }
821            k if k == syntax_kind_ext::TYPE_ASSERTION
822                || k == syntax_kind_ext::AS_EXPRESSION
823                || k == syntax_kind_ext::SATISFIES_EXPRESSION =>
824            {
825                self.emit_type_assertion_expression(node);
826            }
827            k if k == syntax_kind_ext::NON_NULL_EXPRESSION => {
828                self.emit_non_null_expression(node);
829            }
830
831            // Conditional expression
832            k if k == syntax_kind_ext::CONDITIONAL_EXPRESSION => {
833                self.emit_conditional(node);
834            }
835
836            // Array literal
837            k if k == syntax_kind_ext::ARRAY_LITERAL_EXPRESSION => {
838                self.emit_array_literal(node);
839            }
840
841            // Object literal
842            k if k == syntax_kind_ext::OBJECT_LITERAL_EXPRESSION => {
843                self.emit_object_literal(node);
844            }
845
846            // Arrow function
847            k if k == syntax_kind_ext::ARROW_FUNCTION => {
848                self.emit_arrow_function(node, idx);
849            }
850
851            // Function expression
852            k if k == syntax_kind_ext::FUNCTION_EXPRESSION => {
853                self.emit_function_expression(node, idx);
854            }
855
856            // Function declaration
857            k if k == syntax_kind_ext::FUNCTION_DECLARATION => {
858                self.emit_function_declaration(node, idx);
859            }
860
861            // Variable declaration
862            k if k == syntax_kind_ext::VARIABLE_DECLARATION => {
863                self.emit_variable_declaration(node);
864            }
865
866            // Variable declaration list
867            k if k == syntax_kind_ext::VARIABLE_DECLARATION_LIST => {
868                self.emit_variable_declaration_list(node);
869            }
870
871            // Variable statement
872            k if k == syntax_kind_ext::VARIABLE_STATEMENT => {
873                self.emit_variable_statement(node);
874            }
875
876            // Expression statement
877            k if k == syntax_kind_ext::EXPRESSION_STATEMENT => {
878                self.emit_expression_statement(node);
879            }
880
881            // Block
882            k if k == syntax_kind_ext::BLOCK => {
883                self.emit_block(node, idx);
884            }
885
886            // Class static block: `static { ... }`
887            k if k == syntax_kind_ext::CLASS_STATIC_BLOCK_DECLARATION => {
888                self.write("static ");
889                // The static block uses the same data as a Block node
890                self.emit_block(node, idx);
891            }
892
893            // If statement
894            k if k == syntax_kind_ext::IF_STATEMENT => {
895                self.emit_if_statement(node);
896            }
897
898            // While statement
899            k if k == syntax_kind_ext::WHILE_STATEMENT => {
900                self.emit_while_statement(node);
901            }
902
903            // For statement
904            k if k == syntax_kind_ext::FOR_STATEMENT => {
905                self.emit_for_statement(node);
906            }
907
908            // For-in statement
909            k if k == syntax_kind_ext::FOR_IN_STATEMENT => {
910                self.emit_for_in_statement(node);
911            }
912
913            // For-of statement
914            k if k == syntax_kind_ext::FOR_OF_STATEMENT => {
915                self.emit_for_of_statement(node);
916            }
917
918            // Return statement
919            k if k == syntax_kind_ext::RETURN_STATEMENT => {
920                self.emit_return_statement(node);
921            }
922
923            // Class declaration
924            k if k == syntax_kind_ext::CLASS_DECLARATION => {
925                self.emit_class_declaration(node, idx);
926            }
927
928            // Class expression (e.g., `return class extends Base { ... }`)
929            k if k == syntax_kind_ext::CLASS_EXPRESSION => {
930                self.emit_class_declaration(node, idx);
931            }
932
933            // Property assignment
934            k if k == syntax_kind_ext::PROPERTY_ASSIGNMENT => {
935                self.emit_property_assignment(node);
936            }
937
938            // Shorthand property assignment
939            k if k == syntax_kind_ext::SHORTHAND_PROPERTY_ASSIGNMENT => {
940                self.emit_shorthand_property(node);
941            }
942
943            // Spread assignment in object literal: `{ ...expr }` (ES2018+ native spread)
944            // For pre-ES2018 targets this is handled by emit_object_literal_with_object_assign.
945            k if k == syntax_kind_ext::SPREAD_ASSIGNMENT => {
946                if let Some(spread) = self.arena.get_spread(node) {
947                    self.write("...");
948                    self.emit_expression(spread.expression);
949                }
950            }
951
952            // Parameter declaration
953            k if k == syntax_kind_ext::PARAMETER => {
954                self.emit_parameter(node);
955            }
956
957            // Type keywords (for type annotations)
958            k if k == SyntaxKind::NumberKeyword as u16 => self.write("number"),
959            k if k == SyntaxKind::StringKeyword as u16 => self.write("string"),
960            k if k == SyntaxKind::BooleanKeyword as u16 => self.write("boolean"),
961            k if k == SyntaxKind::VoidKeyword as u16 => self.write("void"),
962            k if k == SyntaxKind::AnyKeyword as u16 => self.write("any"),
963            k if k == SyntaxKind::NeverKeyword as u16 => self.write("never"),
964            k if k == SyntaxKind::UnknownKeyword as u16 => self.write("unknown"),
965            k if k == SyntaxKind::UndefinedKeyword as u16 => self.write("undefined"),
966            k if k == SyntaxKind::ObjectKeyword as u16 => self.write("object"),
967            k if k == SyntaxKind::SymbolKeyword as u16 => self.write("symbol"),
968            k if k == SyntaxKind::BigIntKeyword as u16 => self.write("bigint"),
969
970            // Type reference
971            k if k == syntax_kind_ext::TYPE_REFERENCE => {
972                self.emit_type_reference(node);
973            }
974
975            // Array type
976            k if k == syntax_kind_ext::ARRAY_TYPE => {
977                self.emit_array_type(node);
978            }
979
980            // Union type
981            k if k == syntax_kind_ext::UNION_TYPE => {
982                self.emit_union_type(node);
983            }
984
985            // Intersection type
986            k if k == syntax_kind_ext::INTERSECTION_TYPE => {
987                self.emit_intersection_type(node);
988            }
989
990            // Tuple type
991            k if k == syntax_kind_ext::TUPLE_TYPE => {
992                self.emit_tuple_type(node);
993            }
994
995            // Function type
996            k if k == syntax_kind_ext::FUNCTION_TYPE => {
997                self.emit_function_type(node);
998            }
999
1000            // Type literal
1001            k if k == syntax_kind_ext::TYPE_LITERAL => {
1002                self.emit_type_literal(node);
1003            }
1004
1005            // Parenthesized type
1006            k if k == syntax_kind_ext::PARENTHESIZED_TYPE => {
1007                self.emit_parenthesized_type(node);
1008            }
1009
1010            // Empty statement
1011            k if k == syntax_kind_ext::EMPTY_STATEMENT => {
1012                self.write_semicolon();
1013            }
1014
1015            // JSX
1016            k if k == syntax_kind_ext::JSX_ELEMENT => {
1017                self.emit_jsx_element(node);
1018            }
1019            k if k == syntax_kind_ext::JSX_SELF_CLOSING_ELEMENT => {
1020                self.emit_jsx_self_closing_element(node);
1021            }
1022            k if k == syntax_kind_ext::JSX_OPENING_ELEMENT => {
1023                self.emit_jsx_opening_element(node);
1024            }
1025            k if k == syntax_kind_ext::JSX_CLOSING_ELEMENT => {
1026                self.emit_jsx_closing_element(node);
1027            }
1028            k if k == syntax_kind_ext::JSX_FRAGMENT => {
1029                self.emit_jsx_fragment(node);
1030            }
1031            k if k == syntax_kind_ext::JSX_OPENING_FRAGMENT => {
1032                self.write("<>");
1033            }
1034            k if k == syntax_kind_ext::JSX_CLOSING_FRAGMENT => {
1035                self.write("</>");
1036            }
1037            k if k == syntax_kind_ext::JSX_ATTRIBUTES => {
1038                self.emit_jsx_attributes(node);
1039            }
1040            k if k == syntax_kind_ext::JSX_ATTRIBUTE => {
1041                self.emit_jsx_attribute(node);
1042            }
1043            k if k == syntax_kind_ext::JSX_SPREAD_ATTRIBUTE => {
1044                self.emit_jsx_spread_attribute(node);
1045            }
1046            k if k == syntax_kind_ext::JSX_EXPRESSION => {
1047                self.emit_jsx_expression(node);
1048            }
1049            k if k == SyntaxKind::JsxText as u16 => {
1050                self.emit_jsx_text(node);
1051            }
1052            k if k == syntax_kind_ext::JSX_NAMESPACED_NAME => {
1053                self.emit_jsx_namespaced_name(node);
1054            }
1055
1056            // Imports/Exports
1057            k if k == syntax_kind_ext::IMPORT_DECLARATION => {
1058                self.emit_import_declaration(node);
1059            }
1060            k if k == syntax_kind_ext::IMPORT_EQUALS_DECLARATION => {
1061                self.emit_import_equals_declaration(node);
1062            }
1063            k if k == syntax_kind_ext::IMPORT_CLAUSE => {
1064                self.emit_import_clause(node);
1065            }
1066            k if k == syntax_kind_ext::NAMED_IMPORTS || k == syntax_kind_ext::NAMESPACE_IMPORT => {
1067                self.emit_named_imports(node);
1068            }
1069            k if k == syntax_kind_ext::IMPORT_SPECIFIER => {
1070                self.emit_specifier(node);
1071            }
1072            k if k == syntax_kind_ext::EXPORT_DECLARATION => {
1073                self.emit_export_declaration(node);
1074            }
1075            k if k == syntax_kind_ext::NAMESPACE_EXPORT => {
1076                // `* as name` in `export * as name from "..."`
1077                if let Some(data) = self.arena.get_named_imports(node) {
1078                    self.write("* as ");
1079                    self.emit(data.name);
1080                }
1081            }
1082            k if k == syntax_kind_ext::NAMED_EXPORTS => {
1083                self.emit_named_exports(node);
1084            }
1085            k if k == syntax_kind_ext::EXPORT_SPECIFIER => {
1086                self.emit_specifier(node);
1087            }
1088            k if k == syntax_kind_ext::EXPORT_ASSIGNMENT => {
1089                self.emit_export_assignment(node);
1090            }
1091
1092            // Additional statements
1093            k if k == syntax_kind_ext::THROW_STATEMENT => {
1094                self.emit_throw_statement(node);
1095            }
1096            k if k == syntax_kind_ext::TRY_STATEMENT => {
1097                self.emit_try_statement(node);
1098            }
1099            k if k == syntax_kind_ext::CATCH_CLAUSE => {
1100                self.emit_catch_clause(node);
1101            }
1102            k if k == syntax_kind_ext::SWITCH_STATEMENT => {
1103                self.emit_switch_statement(node);
1104            }
1105            k if k == syntax_kind_ext::CASE_CLAUSE => {
1106                self.emit_case_clause(node);
1107            }
1108            k if k == syntax_kind_ext::DEFAULT_CLAUSE => {
1109                self.emit_default_clause(node);
1110            }
1111            k if k == syntax_kind_ext::CASE_BLOCK => {
1112                self.emit_case_block(node);
1113            }
1114            k if k == syntax_kind_ext::BREAK_STATEMENT => {
1115                self.emit_break_statement(node);
1116            }
1117            k if k == syntax_kind_ext::CONTINUE_STATEMENT => {
1118                self.emit_continue_statement(node);
1119            }
1120            k if k == syntax_kind_ext::LABELED_STATEMENT => {
1121                self.emit_labeled_statement(node);
1122            }
1123            k if k == syntax_kind_ext::DO_STATEMENT => {
1124                self.emit_do_statement(node);
1125            }
1126            k if k == syntax_kind_ext::DEBUGGER_STATEMENT => {
1127                self.emit_debugger_statement(node);
1128            }
1129            k if k == syntax_kind_ext::WITH_STATEMENT => {
1130                self.emit_with_statement(node);
1131            }
1132
1133            // Declarations
1134            k if k == syntax_kind_ext::ENUM_DECLARATION => {
1135                self.emit_enum_declaration(node, idx);
1136            }
1137            k if k == syntax_kind_ext::ENUM_MEMBER => {
1138                self.emit_enum_member(node);
1139            }
1140            k if k == syntax_kind_ext::INTERFACE_DECLARATION => {
1141                // Interface declarations are TypeScript-only - emit only in declaration mode (.d.ts)
1142                if self.ctx.flags.in_declaration_emit {
1143                    self.emit_interface_declaration(node);
1144                } else {
1145                    // Skip comments belonging to erased declarations so they don't
1146                    // get emitted later by gap/before-pos comment handling.
1147                    self.skip_comments_for_erased_node(node);
1148                }
1149            }
1150            k if k == syntax_kind_ext::TYPE_ALIAS_DECLARATION => {
1151                // Type alias declarations are TypeScript-only - emit only in declaration mode (.d.ts)
1152                if self.ctx.flags.in_declaration_emit {
1153                    self.emit_type_alias_declaration(node);
1154                } else {
1155                    self.skip_comments_for_erased_node(node);
1156                }
1157            }
1158            k if k == syntax_kind_ext::MODULE_DECLARATION => {
1159                self.emit_module_declaration(node, idx);
1160            }
1161
1162            // Computed property name: [expr]
1163            k if k == syntax_kind_ext::COMPUTED_PROPERTY_NAME => {
1164                if let Some(computed) = self.arena.get_computed_property(node) {
1165                    self.write("[");
1166                    self.emit(computed.expression);
1167                    // Map closing `]` to its source position.
1168                    // The expression's end points past the expression, so `]`
1169                    // is at the expression's end position (where the expression
1170                    // text ends and `]` begins).
1171                    if let Some(text) = self.source_text_for_map() {
1172                        let expr_end = self
1173                            .arena
1174                            .get(computed.expression)
1175                            .map_or(node.pos + 1, |e| e.end);
1176                        self.pending_source_pos = Some(source_position_from_offset(text, expr_end));
1177                    }
1178                    self.write("]");
1179                }
1180            }
1181
1182            // Class members
1183            k if k == syntax_kind_ext::METHOD_DECLARATION => {
1184                self.emit_method_declaration(node);
1185            }
1186            k if k == syntax_kind_ext::PROPERTY_DECLARATION => {
1187                self.emit_property_declaration(node);
1188            }
1189            k if k == syntax_kind_ext::CONSTRUCTOR => {
1190                self.emit_constructor_declaration(node);
1191            }
1192            k if k == syntax_kind_ext::GET_ACCESSOR => {
1193                self.emit_get_accessor(node);
1194            }
1195            k if k == syntax_kind_ext::SET_ACCESSOR => {
1196                self.emit_set_accessor(node);
1197            }
1198            k if k == syntax_kind_ext::SEMICOLON_CLASS_ELEMENT => {
1199                self.write(";");
1200            }
1201            k if k == syntax_kind_ext::DECORATOR => {
1202                self.emit_decorator(node);
1203            }
1204
1205            // Interface/type members (signatures)
1206            k if k == syntax_kind_ext::PROPERTY_SIGNATURE => {
1207                self.emit_property_signature(node);
1208            }
1209            k if k == syntax_kind_ext::METHOD_SIGNATURE => {
1210                self.emit_method_signature(node);
1211            }
1212            k if k == syntax_kind_ext::CALL_SIGNATURE => {
1213                // Call signatures are TypeScript-only - emit only in declaration mode (.d.ts)
1214                if self.ctx.flags.in_declaration_emit {
1215                    self.emit_call_signature(node);
1216                }
1217            }
1218            k if k == syntax_kind_ext::CONSTRUCT_SIGNATURE => {
1219                // Construct signatures are TypeScript-only - emit only in declaration mode (.d.ts)
1220                if self.ctx.flags.in_declaration_emit {
1221                    self.emit_construct_signature(node);
1222                }
1223            }
1224            k if k == syntax_kind_ext::INDEX_SIGNATURE => {
1225                // Index signatures are TypeScript-only - emit only in declaration mode (.d.ts)
1226                if self.ctx.flags.in_declaration_emit {
1227                    self.emit_index_signature(node);
1228                }
1229            }
1230
1231            // Template literals
1232            k if k == syntax_kind_ext::TAGGED_TEMPLATE_EXPRESSION => {
1233                self.emit_tagged_template_expression(node, idx);
1234            }
1235            k if k == syntax_kind_ext::TEMPLATE_EXPRESSION => {
1236                self.emit_template_expression(node);
1237            }
1238            k if k == SyntaxKind::NoSubstitutionTemplateLiteral as u16 => {
1239                self.emit_no_substitution_template(node);
1240            }
1241            k if k == syntax_kind_ext::TEMPLATE_SPAN => {
1242                self.emit_template_span(node);
1243            }
1244            k if k == SyntaxKind::TemplateHead as u16 => {
1245                self.emit_template_head(node);
1246            }
1247            k if k == SyntaxKind::TemplateMiddle as u16 => {
1248                self.emit_template_middle(node);
1249            }
1250            k if k == SyntaxKind::TemplateTail as u16 => {
1251                self.emit_template_tail(node);
1252            }
1253
1254            // Yield/Await/Spread
1255            k if k == syntax_kind_ext::YIELD_EXPRESSION => {
1256                self.emit_yield_expression(node);
1257            }
1258            k if k == syntax_kind_ext::AWAIT_EXPRESSION => {
1259                self.emit_await_expression(node);
1260            }
1261            k if k == syntax_kind_ext::SPREAD_ELEMENT => {
1262                self.emit_spread_element(node);
1263            }
1264
1265            // Source file
1266            k if k == syntax_kind_ext::SOURCE_FILE => {
1267                self.emit_source_file(node, idx);
1268            }
1269
1270            // Other tokens and keywords - emit their text
1271            k if k == SyntaxKind::ThisKeyword as u16 => {
1272                // Check for SubstituteThis directive from lowering pass (Phase C)
1273                // Directive approach is now the only path (fallback removed)
1274                if let Some(TransformDirective::SubstituteThis { capture_name }) =
1275                    self.transforms.get(idx)
1276                {
1277                    let name = std::sync::Arc::clone(capture_name);
1278                    self.write(&name);
1279                } else {
1280                    self.write("this");
1281                }
1282            }
1283            k if k == SyntaxKind::SuperKeyword as u16 => self.write("super"),
1284            k if k == SyntaxKind::ImportKeyword as u16 => self.write("import"),
1285
1286            // Binding patterns (for destructuring)
1287            k if k == syntax_kind_ext::OBJECT_BINDING_PATTERN => {
1288                // When emitting as-is (non-ES5 or for parameters), just emit the pattern
1289                self.emit_object_binding_pattern(node);
1290            }
1291            k if k == syntax_kind_ext::ARRAY_BINDING_PATTERN => {
1292                self.emit_array_binding_pattern(node);
1293            }
1294            k if k == syntax_kind_ext::BINDING_ELEMENT => {
1295                self.emit_binding_element(node);
1296            }
1297
1298            // ExpressionWithTypeArguments / instantiation expression:
1299            // Strip type arguments and emit just the expression, wrapped in
1300            // parentheses to preserve semantics (e.g. `obj.fn<T>` → `(obj.fn)`).
1301            k if k == syntax_kind_ext::EXPRESSION_WITH_TYPE_ARGUMENTS => {
1302                if let Some(data) = self.arena.get_expr_type_args(node) {
1303                    self.write("(");
1304                    self.emit(data.expression);
1305                    self.write(")");
1306                }
1307            }
1308
1309            // Default: do nothing (or handle other cases as needed)
1310            _ => {}
1311        }
1312    }
1313}
1314
1315// =============================================================================
1316// Operator Text Helper
1317// =============================================================================
1318
1319pub(super) use crate::transforms::emit_utils::is_valid_identifier_name;
1320
1321const fn get_operator_text(op: u16) -> &'static str {
1322    crate::transforms::emit_utils::operator_to_str(op)
1323}