oxc_codegen 0.128.0

A collection of JavaScript tools written in Rust.
Documentation
use std::borrow::Cow;

use rustc_hash::{FxHashMap, FxHashSet};

use oxc_ast::{Comment, CommentKind, ast::Program};
use oxc_syntax::line_terminator::LineTerminatorSplitter;

use crate::{Codegen, LegalComment, options::CommentOptions};

pub type CommentsMap = FxHashMap</* attached_to */ u32, Vec<Comment>>;

impl Codegen<'_> {
    pub(crate) fn build_comments(&mut self, comments: &[Comment]) {
        if self.options.comments == CommentOptions::disabled() {
            return;
        }
        for comment in comments {
            // Omit pure comments because they are handled separately.
            if comment.is_pure() || comment.is_no_side_effects() {
                continue;
            }
            let mut add = false;
            if comment.is_leading() {
                if comment.is_legal() && self.options.print_legal_comment() {
                    add = true;
                }
                if comment.is_jsdoc() && self.options.print_jsdoc_comment() {
                    add = true;
                }
                if comment.is_annotation() && self.options.print_annotation_comment() {
                    add = true;
                }
                if comment.is_normal() && self.options.print_normal_comment() {
                    add = true;
                }
            }
            if add {
                self.comments.entry(comment.attached_to).or_default().push(*comment);
            }
        }
    }

    pub(crate) fn has_comment(&self, start: u32) -> bool {
        self.comments.contains_key(&start)
    }

    pub(crate) fn print_leading_comments(&mut self, start: u32) {
        if let Some(comments) = self.comments.remove(&start) {
            self.print_comments(&comments);
        }
    }

    pub(crate) fn get_comments(&mut self, start: u32) -> Option<Vec<Comment>> {
        if self.comments.is_empty() {
            return None;
        }
        self.comments.remove(&start)
    }

    #[inline]
    pub(crate) fn print_comments_at(&mut self, start: u32) {
        if let Some(comments) = self.get_comments(start) {
            self.print_comments(&comments);
        }
    }

    /// Print comments attached to any position in the given range `(start, end)` (exclusive).
    /// Returns `true` if any comments were printed.
    pub(crate) fn print_comments_in_range(&mut self, start: u32, end: u32) -> bool {
        if self.comments.is_empty() {
            return false;
        }
        // Find and remove the first key in the range.
        let key = self.comments.keys().find(|&&k| k > start && k < end).copied();
        if let Some(key) = key {
            let comments = self.comments.remove(&key).unwrap();
            self.print_comments(&comments);
            return true;
        }
        false
    }

    pub(crate) fn print_expr_comments(&mut self, start: u32) -> bool {
        if self.comments.is_empty() {
            return false;
        }
        let Some(comments) = self.comments.remove(&start) else { return false };

        for comment in &comments {
            self.print_hard_newline();
            self.print_indent();
            self.print_comment(comment);
        }

        if comments.is_empty() {
            false
        } else {
            self.print_hard_newline();
            true
        }
    }

    pub(crate) fn print_comments(&mut self, comments: &[Comment]) {
        let Some((first, rest)) = comments.split_first() else {
            return;
        };

        if first.preceded_by_newline() {
            // Skip printing newline if this comment is already on a newline.
            if let Some(b) = self.last_byte() {
                match b {
                    b'\n' => self.print_indent(),
                    b'\t' => { /* noop */ }
                    _ => {
                        self.print_hard_newline();
                        self.print_indent();
                    }
                }
            }
        } else {
            self.print_indent();
        }
        self.print_comment(first);

        if let Some((last, middle)) = rest.split_last() {
            for comment in middle {
                if comment.preceded_by_newline() {
                    self.print_hard_newline();
                    self.print_indent();
                } else if comment.is_legal() {
                    self.print_hard_newline();
                } else {
                    self.print_soft_space();
                }
                self.print_comment(comment);
            }

            if last.preceded_by_newline() {
                self.print_hard_newline();
                self.print_indent();
            } else if last.is_legal() {
                self.print_hard_newline();
            } else {
                self.print_soft_space();
            }
            self.print_comment(last);

            if last.is_line() || last.followed_by_newline() {
                self.print_hard_newline();
            } else {
                self.print_next_indent_as_space = true;
            }
        } else if first.is_line() || first.followed_by_newline() {
            self.print_hard_newline();
        } else {
            self.print_next_indent_as_space = true;
        }
    }

    fn print_comment(&mut self, comment: &Comment) {
        let Some(source_text) = self.source_text else {
            return;
        };
        let comment_source = comment.span.source_text(source_text);
        match comment.kind {
            CommentKind::Line | CommentKind::SingleLineBlock => {
                self.print_str_escaping_script_close_tag(comment_source);
            }
            CommentKind::MultiLineBlock => {
                for line in LineTerminatorSplitter::new(comment_source) {
                    if !line.starts_with("/*") {
                        self.print_indent();
                    }
                    self.print_str_escaping_script_close_tag(line.trim_start());
                    if !line.ends_with("*/") {
                        self.print_hard_newline();
                    }
                }
            }
        }
    }

    /// Handle Eof / Linked / External Comments.
    /// Return a list of comments of linked or external.
    pub(crate) fn handle_eof_linked_or_external_comments(
        &mut self,
        program: &Program<'_>,
    ) -> Vec<Comment> {
        let legal_comments = &self.options.comments.legal;
        if matches!(legal_comments, LegalComment::None | LegalComment::Inline) {
            return vec![];
        }

        // Dedupe legal comments for smaller output size.
        let mut set = FxHashSet::default();
        let mut comments = vec![];

        let source_text = program.source_text;
        for comment in program.comments.iter().filter(|c| c.is_legal()) {
            let mut text = Cow::Borrowed(comment.span.source_text(source_text));
            if comment.is_multiline_block() {
                let mut buffer = String::with_capacity(text.len());
                // Print block comments with our own indentation.
                for line in LineTerminatorSplitter::new(&text) {
                    if !line.starts_with("/*") {
                        buffer.push('\t');
                    }
                    buffer.push_str(line.trim_start());
                    if !line.ends_with("*/") {
                        buffer.push('\n');
                    }
                }
                text = Cow::Owned(buffer);
            }
            if set.insert(text) {
                comments.push(*comment);
            }
        }

        if comments.is_empty() {
            return vec![];
        }

        match legal_comments {
            LegalComment::Eof => {
                self.print_hard_newline();
                // Clear the flag to ensure consistent formatting for all EOF comments
                self.print_next_indent_as_space = false;
                for c in comments {
                    self.print_comment(&c);
                    self.print_hard_newline();
                }
                vec![]
            }
            LegalComment::Linked(path) => {
                let path = path.clone();
                self.print_hard_newline();
                self.print_str("/*! For license information please see ");
                self.print_str(&path);
                self.print_str(" */");
                comments
            }
            LegalComment::External => comments,
            LegalComment::None | LegalComment::Inline => unreachable!(),
        }
    }
}