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< 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 {
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);
}
}
pub(crate) fn print_comments_in_range(&mut self, start: u32, end: u32) -> bool {
if self.comments.is_empty() {
return false;
}
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() {
if let Some(b) = self.last_byte() {
match b {
b'\n' => self.print_indent(),
b'\t' => { }
_ => {
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();
}
}
}
}
}
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![];
}
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());
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();
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!(),
}
}
}