1use std::{borrow::Cow, cmp, slice};
7
8use cow_utils::CowUtils;
9
10use oxc_ast::ast::*;
11use oxc_data_structures::{code_buffer::CodeBuffer, stack::Stack};
12use oxc_index::IndexVec;
13use oxc_semantic::Scoping;
14use oxc_span::{CompactStr, GetSpan, Span};
15use oxc_syntax::{
16 class::ClassId,
17 identifier::{is_identifier_part, is_identifier_part_ascii},
18 operator::{BinaryOperator, UnaryOperator, UpdateOperator},
19 precedence::Precedence,
20};
21use rustc_hash::FxHashMap;
22
23mod binary_expr_visitor;
24mod comment;
25mod context;
26mod r#gen;
27mod operator;
28mod options;
29#[cfg(feature = "sourcemap")]
30mod sourcemap_builder;
31mod str;
32
33use binary_expr_visitor::BinaryExpressionVisitor;
34use comment::CommentsMap;
35use operator::Operator;
36#[cfg(feature = "sourcemap")]
37use sourcemap_builder::SourcemapBuilder;
38use str::{Quote, cold_branch, is_script_close_tag};
39
40pub use context::Context;
41pub use r#gen::{Gen, GenExpr};
42pub use options::{CodegenOptions, CommentOptions, LegalComment};
43
44pub use oxc_data_structures::code_buffer::IndentChar;
46
47#[non_exhaustive]
49pub struct CodegenReturn {
50 pub code: String,
52
53 #[cfg(feature = "sourcemap")]
57 pub map: Option<oxc_sourcemap::SourceMap>,
58
59 pub legal_comments: Vec<Comment>,
61}
62
63pub struct Codegen<'a> {
82 pub(crate) options: CodegenOptions,
83
84 source_text: Option<&'a str>,
86
87 scoping: Option<Scoping>,
88
89 private_member_mappings: Option<IndexVec<ClassId, FxHashMap<String, CompactStr>>>,
91
92 code: CodeBuffer,
94
95 prev_op_end: usize,
97 prev_reg_exp_end: usize,
98 need_space_before_dot: usize,
99 print_next_indent_as_space: bool,
100 binary_expr_stack: Stack<BinaryExpressionVisitor<'a>>,
101 class_stack: Stack<ClassId>,
102 next_class_id: ClassId,
103 is_jsx: bool,
106
107 needs_semicolon: bool,
109
110 prev_op: Option<Operator>,
111
112 start_of_stmt: usize,
113 start_of_arrow_expr: usize,
114 start_of_default_export: usize,
115
116 indent: u32,
118
119 quote: Quote,
121
122 comments: CommentsMap,
124
125 #[cfg(feature = "sourcemap")]
126 sourcemap_builder: Option<SourcemapBuilder<'a>>,
127}
128
129impl Default for Codegen<'_> {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl<'a> From<Codegen<'a>> for String {
136 fn from(val: Codegen<'a>) -> Self {
137 val.into_source_text()
138 }
139}
140
141impl<'a> From<Codegen<'a>> for Cow<'a, str> {
142 fn from(val: Codegen<'a>) -> Self {
143 Cow::Owned(val.into_source_text())
144 }
145}
146
147impl<'a> Codegen<'a> {
149 #[must_use]
153 pub fn new() -> Self {
154 let options = CodegenOptions::default();
155 Self {
156 options,
157 source_text: None,
158 scoping: None,
159 private_member_mappings: None,
160 code: CodeBuffer::default(),
161 needs_semicolon: false,
162 need_space_before_dot: 0,
163 print_next_indent_as_space: false,
164 binary_expr_stack: Stack::with_capacity(12),
165 class_stack: Stack::with_capacity(4),
166 next_class_id: ClassId::from_usize(0),
167 prev_op_end: 0,
168 prev_reg_exp_end: 0,
169 prev_op: None,
170 start_of_stmt: 0,
171 start_of_arrow_expr: 0,
172 start_of_default_export: 0,
173 is_jsx: false,
174 indent: 0,
175 quote: Quote::Double,
176 comments: CommentsMap::default(),
177 #[cfg(feature = "sourcemap")]
178 sourcemap_builder: None,
179 }
180 }
181
182 #[must_use]
184 pub fn with_options(mut self, options: CodegenOptions) -> Self {
185 self.quote = if options.single_quote { Quote::Single } else { Quote::Double };
186 self.code = CodeBuffer::with_indent(options.indent_char, options.indent_width);
187 self.options = options;
188 self
189 }
190
191 #[must_use]
193 pub fn with_source_text(mut self, source_text: &'a str) -> Self {
194 self.source_text = Some(source_text);
195 self
196 }
197
198 #[must_use]
202 pub fn with_scoping(mut self, scoping: Option<Scoping>) -> Self {
203 self.scoping = scoping;
204 self
205 }
206
207 #[must_use]
212 pub fn with_private_member_mappings(
213 mut self,
214 mappings: Option<IndexVec<ClassId, FxHashMap<String, CompactStr>>>,
215 ) -> Self {
216 self.private_member_mappings = mappings;
217 self
218 }
219
220 #[must_use]
224 pub fn build(mut self, program: &Program<'a>) -> CodegenReturn {
225 self.quote = if self.options.single_quote { Quote::Single } else { Quote::Double };
226 self.source_text = Some(program.source_text);
227 self.indent = self.options.initial_indent;
228 self.code.reserve(program.source_text.len());
229 self.build_comments(&program.comments);
230 #[cfg(feature = "sourcemap")]
231 if let Some(path) = &self.options.source_map_path {
232 self.sourcemap_builder = Some(SourcemapBuilder::new(path, program.source_text));
233 }
234 program.print(&mut self, Context::default());
235 let legal_comments = self.handle_eof_linked_or_external_comments(program);
236 let code = self.code.into_string();
237 #[cfg(feature = "sourcemap")]
238 let map = self.sourcemap_builder.map(SourcemapBuilder::into_sourcemap);
239 CodegenReturn {
240 code,
241 #[cfg(feature = "sourcemap")]
242 map,
243 legal_comments,
244 }
245 }
246
247 #[must_use]
256 pub fn into_source_text(self) -> String {
257 self.code.into_string()
258 }
259
260 #[inline]
265 pub fn print_ascii_byte(&mut self, byte: u8) {
266 self.code.print_ascii_byte(byte);
267 }
268
269 #[inline]
271 pub fn print_str(&mut self, s: &str) {
272 self.code.print_str(s);
273 }
274
275 #[inline]
277 pub fn print_str_escaping_script_close_tag(&mut self, s: &str) {
278 let bytes = s.as_bytes();
284 let mut consumed = 0;
285
286 let mut search_bytes = |mut ptr: *const u8, last_ptr| {
297 loop {
298 let byte = unsafe { *ptr.as_ref().unwrap_unchecked() };
301 if byte == b'<' {
302 let slice = unsafe { slice::from_raw_parts(ptr, 8) };
305 if is_script_close_tag(slice) {
306 unsafe {
313 let index = ptr.offset_from_unsigned(bytes.as_ptr());
314 let before = bytes.get_unchecked(consumed..=index);
315 self.code.print_bytes_unchecked(before);
316
317 consumed = index + 2;
319 }
320 self.print_str("\\/");
321 }
324 }
325
326 if ptr == last_ptr {
327 break;
328 }
329 ptr = unsafe { ptr.add(1) };
331 }
332 };
333
334 let mut chunks = bytes.chunks_exact(16);
336 for (chunk_index, chunk) in chunks.by_ref().enumerate() {
337 #[expect(clippy::missing_panics_doc, reason = "infallible")]
338 let chunk: &[u8; 16] = chunk.try_into().unwrap();
339
340 let mut contains_lt = false;
342 for &byte in chunk {
343 if byte == b'<' {
344 contains_lt = true;
345 }
346 }
347
348 if contains_lt {
349 cold_branch(|| unsafe {
359 let index = chunk_index * 16;
360 let remaining_bytes = bytes.len() - index;
361 let last_offset = cmp::min(remaining_bytes - 8, 15);
362 let ptr = bytes.as_ptr().add(index);
363 let last_ptr = ptr.add(last_offset);
364 search_bytes(ptr, last_ptr);
365 });
366 }
367 }
368
369 let last_chunk = chunks.remainder();
372 if last_chunk.len() >= 8 {
373 let ptr = last_chunk.as_ptr();
374 let last_ptr = unsafe { ptr.add(last_chunk.len() - 8) };
377 search_bytes(ptr, last_ptr);
378 }
379
380 unsafe {
382 let remaining = bytes.get_unchecked(consumed..);
383 self.code.print_bytes_unchecked(remaining);
384 }
385 }
386
387 #[inline]
390 pub fn print_expression(&mut self, expr: &Expression<'_>) {
391 expr.print_expr(self, Precedence::Lowest, Context::empty());
392 }
393}
394
395impl<'a> Codegen<'a> {
397 fn code(&self) -> &CodeBuffer {
398 &self.code
399 }
400
401 fn code_len(&self) -> usize {
402 self.code().len()
403 }
404
405 #[inline]
406 fn print_soft_space(&mut self) {
407 if !self.options.minify {
408 self.print_ascii_byte(b' ');
409 }
410 }
411
412 #[inline]
413 fn print_hard_space(&mut self) {
414 self.print_ascii_byte(b' ');
415 }
416
417 #[inline]
418 fn print_soft_newline(&mut self) {
419 if !self.options.minify {
420 self.print_ascii_byte(b'\n');
421 }
422 }
423
424 #[inline]
425 fn print_hard_newline(&mut self) {
426 self.print_ascii_byte(b'\n');
427 }
428
429 #[inline]
430 fn print_semicolon(&mut self) {
431 self.print_ascii_byte(b';');
432 }
433
434 #[inline]
435 fn print_comma(&mut self) {
436 self.print_ascii_byte(b',');
437 }
438
439 #[inline]
440 fn print_space_before_identifier(&mut self) {
441 let Some(byte) = self.last_byte() else { return };
442
443 if self.prev_reg_exp_end != self.code.len() {
444 let is_identifier = if byte.is_ascii() {
445 is_identifier_part_ascii(byte as char)
447 } else {
448 is_identifier_part(self.last_char().unwrap())
449 };
450 if !is_identifier {
451 return;
452 }
453 }
454
455 self.print_hard_space();
456 }
457
458 #[inline]
459 fn last_byte(&self) -> Option<u8> {
460 self.code.last_byte()
461 }
462
463 #[inline]
464 fn last_char(&self) -> Option<char> {
465 self.code.last_char()
466 }
467
468 #[inline]
469 fn indent(&mut self) {
470 if !self.options.minify {
471 self.indent += 1;
472 }
473 }
474
475 #[inline]
476 fn dedent(&mut self) {
477 if !self.options.minify {
478 self.indent -= 1;
479 }
480 }
481
482 #[inline]
483 fn enter_class(&mut self) {
484 let class_id = self.next_class_id;
485 self.next_class_id = ClassId::from_usize(self.next_class_id.index() + 1);
486 self.class_stack.push(class_id);
487 }
488
489 #[inline]
490 fn exit_class(&mut self) {
491 self.class_stack.pop();
492 }
493
494 #[inline]
495 fn current_class_ids(&self) -> impl Iterator<Item = ClassId> {
496 self.class_stack.iter().rev().copied()
497 }
498
499 #[inline]
500 fn wrap<F: FnMut(&mut Self)>(&mut self, wrap: bool, mut f: F) {
501 if wrap {
502 self.print_ascii_byte(b'(');
503 }
504 f(self);
505 if wrap {
506 self.print_ascii_byte(b')');
507 }
508 }
509
510 #[inline]
511 fn print_indent(&mut self) {
512 if self.options.minify {
513 return;
514 }
515 if self.print_next_indent_as_space {
516 self.print_hard_space();
517 self.print_next_indent_as_space = false;
518 return;
519 }
520 self.code.print_indent(self.indent as usize);
521 }
522
523 #[inline]
524 fn print_semicolon_after_statement(&mut self) {
525 if self.options.minify {
526 self.needs_semicolon = true;
527 } else {
528 self.print_str(";\n");
529 }
530 }
531
532 #[inline]
533 fn print_semicolon_if_needed(&mut self) {
534 if self.needs_semicolon {
535 self.print_semicolon();
536 self.needs_semicolon = false;
537 }
538 }
539
540 #[inline]
541 fn print_ellipsis(&mut self) {
542 self.print_str("...");
543 }
544
545 #[inline]
546 fn print_colon(&mut self) {
547 self.print_ascii_byte(b':');
548 }
549
550 #[inline]
551 fn print_equal(&mut self) {
552 self.print_ascii_byte(b'=');
553 }
554
555 fn print_curly_braces<F: FnOnce(&mut Self)>(&mut self, span: Span, single_line: bool, op: F) {
556 self.add_source_mapping(span);
557 self.print_ascii_byte(b'{');
558 if !single_line {
559 self.print_soft_newline();
560 self.indent();
561 }
562 op(self);
563 if !single_line {
564 self.dedent();
565 self.print_indent();
566 }
567 self.print_ascii_byte(b'}');
568 }
569
570 fn print_block_start(&mut self, span: Span) {
571 self.add_source_mapping(span);
572 self.print_ascii_byte(b'{');
573 self.print_soft_newline();
574 self.indent();
575 }
576
577 fn print_block_end(&mut self, _span: Span) {
578 self.dedent();
579 self.print_indent();
580 self.print_ascii_byte(b'}');
581 }
582
583 fn print_body(&mut self, stmt: &Statement<'_>, need_space: bool, ctx: Context) {
584 match stmt {
585 Statement::BlockStatement(stmt) => {
586 self.print_soft_space();
587 self.print_block_statement(stmt, ctx);
588 self.print_soft_newline();
589 }
590 Statement::EmptyStatement(_) => {
591 self.print_semicolon();
592 self.print_soft_newline();
593 }
594 stmt => {
595 if need_space && self.options.minify {
596 self.print_hard_space();
597 }
598 self.print_next_indent_as_space = true;
599 stmt.print(self, ctx);
600 }
601 }
602 }
603
604 fn print_block_statement(&mut self, stmt: &BlockStatement<'_>, ctx: Context) {
605 self.print_curly_braces(stmt.span, stmt.body.is_empty(), |p| {
606 for stmt in &stmt.body {
607 p.print_semicolon_if_needed();
608 stmt.print(p, ctx);
609 }
610 });
611 self.needs_semicolon = false;
612 }
613
614 fn print_directives_and_statements(
615 &mut self,
616 directives: &[Directive<'_>],
617 stmts: &[Statement<'_>],
618 ctx: Context,
619 ) {
620 for directive in directives {
621 directive.print(self, ctx);
622 }
623 let Some((first, rest)) = stmts.split_first() else {
624 return;
625 };
626
627 let mut first_needs_parens = false;
629 if directives.is_empty()
630 && !self.options.minify
631 && let Statement::ExpressionStatement(s) = first
632 {
633 let s = s.expression.without_parentheses();
634 if matches!(s, Expression::StringLiteral(_)) {
635 first_needs_parens = true;
636 self.print_ascii_byte(b'(');
637 s.print_expr(self, Precedence::Lowest, ctx);
638 self.print_ascii_byte(b')');
639 self.print_semicolon_after_statement();
640 }
641 }
642
643 if !first_needs_parens {
644 first.print(self, ctx);
645 }
646
647 for stmt in rest {
648 self.print_semicolon_if_needed();
649 stmt.print(self, ctx);
650 }
651 }
652
653 #[inline]
654 fn print_list<T: Gen>(&mut self, items: &[T], ctx: Context) {
655 let Some((first, rest)) = items.split_first() else {
656 return;
657 };
658 first.print(self, ctx);
659 for item in rest {
660 self.print_comma();
661 self.print_soft_space();
662 item.print(self, ctx);
663 }
664 }
665
666 #[inline]
667 fn print_expressions<T: GenExpr>(&mut self, items: &[T], precedence: Precedence, ctx: Context) {
668 let Some((first, rest)) = items.split_first() else {
669 return;
670 };
671 first.print_expr(self, precedence, ctx);
672 for item in rest {
673 self.print_comma();
674 self.print_soft_space();
675 item.print_expr(self, precedence, ctx);
676 }
677 }
678
679 fn print_arguments(&mut self, span: Span, arguments: &[Argument<'_>], ctx: Context) {
680 self.print_ascii_byte(b'(');
681
682 let has_comment_before_right_paren = span.end > 0 && self.has_comment(span.end - 1);
683
684 let has_comment = has_comment_before_right_paren
685 || arguments.iter().any(|item| self.has_comment(item.span().start));
686
687 if has_comment {
688 self.indent();
689 self.print_list_with_comments(arguments, ctx);
690 if !has_comment_before_right_paren
692 || (span.end > 0 && !self.print_expr_comments(span.end - 1))
693 {
694 self.print_soft_newline();
695 }
696 self.dedent();
697 self.print_indent();
698 } else {
699 self.print_list(arguments, ctx);
700 }
701 self.print_ascii_byte(b')');
702 self.add_source_mapping_end(span);
703 }
704
705 fn print_list_with_comments(&mut self, items: &[Argument<'_>], ctx: Context) {
706 let Some((first, rest)) = items.split_first() else {
707 return;
708 };
709 if self.print_expr_comments(first.span().start) {
710 self.print_indent();
711 } else {
712 self.print_soft_newline();
713 self.print_indent();
714 }
715 first.print(self, ctx);
716 for item in rest {
717 self.print_comma();
718 if self.print_expr_comments(item.span().start) {
719 self.print_indent();
720 } else {
721 self.print_soft_newline();
722 self.print_indent();
723 }
724 item.print(self, ctx);
725 }
726 }
727
728 fn get_identifier_reference_name(&self, reference: &IdentifierReference<'a>) -> &'a str {
729 if let Some(scoping) = &self.scoping
730 && let Some(reference_id) = reference.reference_id.get()
731 && let Some(name) = scoping.get_reference_name(reference_id)
732 {
733 return unsafe { std::mem::transmute_copy(&name) };
735 }
736 reference.name.as_str()
737 }
738
739 fn get_binding_identifier_name(&self, ident: &BindingIdentifier<'a>) -> &'a str {
740 if let Some(scoping) = &self.scoping
741 && let Some(symbol_id) = ident.symbol_id.get()
742 {
743 let name = scoping.symbol_name(symbol_id);
744 return unsafe { std::mem::transmute_copy(&name) };
746 }
747 ident.name.as_str()
748 }
749
750 fn print_space_before_operator(&mut self, next: Operator) {
751 if self.prev_op_end != self.code.len() {
752 return;
753 }
754 let Some(prev) = self.prev_op else { return };
755 let bin_op_add = Operator::Binary(BinaryOperator::Addition);
763 let bin_op_sub = Operator::Binary(BinaryOperator::Subtraction);
764 let un_op_pos = Operator::Unary(UnaryOperator::UnaryPlus);
765 let un_op_pre_inc = Operator::Update(UpdateOperator::Increment);
766 let un_op_neg = Operator::Unary(UnaryOperator::UnaryNegation);
767 let un_op_pre_dec = Operator::Update(UpdateOperator::Decrement);
768 let un_op_post_dec = Operator::Update(UpdateOperator::Decrement);
769 let bin_op_gt = Operator::Binary(BinaryOperator::GreaterThan);
770 let un_op_not = Operator::Unary(UnaryOperator::LogicalNot);
771 if ((prev == bin_op_add || prev == un_op_pos)
772 && (next == bin_op_add || next == un_op_pos || next == un_op_pre_inc))
773 || ((prev == bin_op_sub || prev == un_op_neg)
774 && (next == bin_op_sub || next == un_op_neg || next == un_op_pre_dec))
775 || (prev == un_op_post_dec && next == bin_op_gt)
776 || (prev == un_op_not
777 && next == un_op_pre_dec
778 && self.code.peek_nth_byte_back(1) == Some(b'<'))
781 {
782 self.print_hard_space();
783 }
784 }
785
786 fn print_non_negative_float(&mut self, num: f64) {
787 let mut buffer = dragonbox_ecma::Buffer::new();
789 if num < 1000.0 && num.fract() == 0.0 {
790 self.print_str(buffer.format(num));
791 self.need_space_before_dot = self.code_len();
792 } else {
793 self.print_minified_number(num, &mut buffer);
794 }
795 }
796
797 fn print_decorators(&mut self, decorators: &[Decorator<'_>], ctx: Context) {
798 for decorator in decorators {
799 decorator.print(self, ctx);
800 self.print_hard_space();
801 }
802 }
803
804 #[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
809 fn print_minified_number(&mut self, num: f64, buffer: &mut dragonbox_ecma::Buffer) {
810 if num < 1000.0 && num.fract() == 0.0 {
811 self.print_str(buffer.format(num));
812 self.need_space_before_dot = self.code_len();
813 return;
814 }
815
816 let mut s = buffer.format(num);
817
818 if s.starts_with("0.") {
819 s = &s[1..];
820 }
821
822 let mut best_candidate = s.cow_replacen("e+", "e", 1);
823 let mut is_hex = false;
824
825 if num.fract() == 0.0 {
827 let hex_candidate = format!("0x{:x}", num as u128);
829 if hex_candidate.len() < best_candidate.len() {
830 is_hex = true;
831 best_candidate = hex_candidate.into();
832 }
833 }
834 else if best_candidate.starts_with(".0") {
836 if let Some(i) = best_candidate.bytes().skip(2).position(|c| c != b'0') {
838 let len = i + 2; let digits = &best_candidate[len..];
840 let exp = digits.len() + len - 1;
841 let exp_str_len = itoa::Buffer::new().format(exp).len();
842 let expected_len = digits.len() + 2 + exp_str_len;
844 if expected_len < best_candidate.len() {
845 best_candidate = format!("{digits}e-{exp}").into();
846 debug_assert_eq!(best_candidate.len(), expected_len);
847 }
848 }
849 }
850
851 if !is_hex
855 && best_candidate.ends_with('0')
856 && let Some(len) = best_candidate.bytes().rev().position(|c| c != b'0')
857 {
858 let base = &best_candidate[0..best_candidate.len() - len];
859 let exp_str_len = itoa::Buffer::new().format(len).len();
860 let expected_len = base.len() + 1 + exp_str_len;
862 if expected_len < best_candidate.len() {
863 best_candidate = format!("{base}e{len}").into();
864 debug_assert_eq!(best_candidate.len(), expected_len);
865 }
866 }
867
868 if let Some((integer, point, exponent)) = best_candidate
870 .split_once('.')
871 .and_then(|(a, b)| b.split_once('e').map(|e| (a, e.0, e.1)))
872 {
873 let new_expr = exponent.parse::<isize>().unwrap() - point.len() as isize;
874 let new_exp_str_len = itoa::Buffer::new().format(new_expr).len();
875 let expected_len = integer.len() + point.len() + 1 + new_exp_str_len;
877 if expected_len < best_candidate.len() {
878 best_candidate = format!("{integer}{point}e{new_expr}").into();
879 debug_assert_eq!(best_candidate.len(), expected_len);
880 }
881 }
882
883 self.print_str(&best_candidate);
885 if !best_candidate.bytes().any(|b| matches!(b, b'.' | b'e' | b'x')) {
886 self.need_space_before_dot = self.code_len();
887 }
888 }
889
890 #[cfg(feature = "sourcemap")]
891 fn add_source_mapping(&mut self, span: Span) {
892 if let Some(sourcemap_builder) = self.sourcemap_builder.as_mut()
893 && !span.is_empty()
894 {
895 sourcemap_builder.add_source_mapping(self.code.as_bytes(), span.start, None);
896 }
897 }
898
899 #[cfg(not(feature = "sourcemap"))]
900 #[inline]
901 #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
902 fn add_source_mapping(&mut self, _span: Span) {}
903
904 #[cfg(feature = "sourcemap")]
905 fn add_source_mapping_end(&mut self, span: Span) {
906 if let Some(sourcemap_builder) = self.sourcemap_builder.as_mut()
907 && !span.is_empty()
908 {
909 if let Some(source_text) = self.source_text {
914 #[expect(clippy::cast_possible_truncation)]
915 if span.end >= source_text.len() as u32 {
916 return;
917 }
918 }
919 sourcemap_builder.add_source_mapping(self.code.as_bytes(), span.end, None);
920 }
921 }
922
923 #[cfg(not(feature = "sourcemap"))]
924 #[inline]
925 #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
926 fn add_source_mapping_end(&mut self, _span: Span) {}
927
928 #[cfg(feature = "sourcemap")]
929 fn add_source_mapping_for_name(&mut self, span: Span, name: &str) {
930 if let Some(sourcemap_builder) = self.sourcemap_builder.as_mut()
931 && !span.is_empty()
932 {
933 sourcemap_builder.add_source_mapping_for_name(self.code.as_bytes(), span, name);
934 }
935 }
936
937 #[cfg(not(feature = "sourcemap"))]
938 #[inline]
939 #[expect(clippy::needless_pass_by_ref_mut, clippy::unused_self)]
940 fn add_source_mapping_for_name(&mut self, _span: Span, _name: &str) {}
941}