1use std::string::String;23use dprint_core::formatting::PrintItems;4use jrsonnet_rowan_parser::{AstToken, nodes::TriviaKind};56use crate::{children::ChildTrivia, p, pi};78pub enum CommentLocation {9 10 AboveItem,11 12 ItemInline,13 14 EndOfItems,15}1617#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]18pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation, out: &mut PrintItems) {19 for c in comments {20 let Ok(c) = c else {21 let mut text = c.as_ref().unwrap_err() as &str;22 while !text.is_empty() {23 let pos = text.find(['\n', '\t']).unwrap_or(text.len());24 let sliced = &text[..pos];25 p!(out, string(sliced.to_string()));26 text = &text[pos..];27 if !text.is_empty() {28 match text.as_bytes()[0] {29 b'\n' => p!(out, nl),30 b'\t' => p!(out, tab),31 _ => unreachable!(),32 }33 text = &text[1..];34 }35 }36 continue;37 };38 match c.kind() {39 TriviaKind::Whitespace => {}40 TriviaKind::MultiLineComment => {41 let mut text = c42 .text()43 .strip_prefix("/*")44 .expect("ml comment starts with /*")45 .strip_suffix("*/")46 .expect("ml comment ends with */");47 48 let doc = if text.starts_with('*') {49 text = &text[1..];50 true51 } else {52 false53 };54 55 let mut immediate_start = true;56 let mut lines = text57 .split('\n')58 .map(|l| l.trim_end().to_string())59 .skip_while(|l| {60 if l.is_empty() {61 immediate_start = false;62 true63 } else {64 false65 }66 })67 .collect::<Vec<_>>();68 while lines.last().is_some_and(String::is_empty) {69 lines.pop();70 }71 if lines.len() == 1 && !doc {72 if matches!(loc, CommentLocation::ItemInline) {73 p!(out, str(" "));74 }75 p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */"));76 if matches!(77 loc,78 CommentLocation::AboveItem | CommentLocation::EndOfItems79 ) {80 p!(out, nl);81 }82 } else if !lines.is_empty() {83 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {84 let offset = a85 .bytes()86 .zip(b.bytes())87 .take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))88 .count();89 &a[..offset]90 }91 92 let mut common_ws_padding = (if immediate_start && lines.len() > 1 {93 common_ws_prefix(&lines[1], &lines[1])94 } else {95 common_ws_prefix(&lines[0], &lines[0])96 })97 .to_string();98 for line in lines99 .iter()100 .skip(if immediate_start { 2 } else { 1 })101 .filter(|l| !l.is_empty())102 {103 common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();104 }105 for line in lines106 .iter_mut()107 .skip(usize::from(immediate_start))108 .filter(|l| !l.is_empty())109 {110 *line = line111 .strip_prefix(&common_ws_padding)112 .expect("all non-empty lines start with this padding")113 .to_string();114 }115116 p!(out, str("/*"));117 if doc {118 p!(out, str("*"));119 }120 p!(out, nl);121 for mut line in lines {122 if doc {123 p!(out, str(" *"));124 }125 if line.is_empty() {126 p!(out, nl);127 } else {128 if doc {129 p!(out, str(" "));130 }131 while let Some(new_line) = line.strip_prefix('\t') {132 if doc {133 p!(out, str(" "));134 } else {135 p!(out, tab);136 }137 line = new_line.to_string();138 }139 p!(out, string(line.clone()) nl);140 }141 }142 if doc {143 p!(out, str(" "));144 }145 p!(out, str("*/") nl);146 }147 }148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 TriviaKind::SingleLineHashComment => {165 if matches!(loc, CommentLocation::ItemInline) {166 p!(out, str(" "));167 }168 p!(out, str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));169 if !matches!(loc, CommentLocation::ItemInline) {170 p!(out, nl);171 }172 }173 TriviaKind::SingleLineSlashComment => {174 if matches!(loc, CommentLocation::ItemInline) {175 p!(out, str(" "));176 }177 p!(out, str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));178 if !matches!(loc, CommentLocation::ItemInline) {179 p!(out, nl);180 }181 }182 183 TriviaKind::ErrorCommentTooShort => p!(out, str("/*/")),184 TriviaKind::ErrorCommentUnterminated => p!(out, string(c.text().to_string())),185 }186 }187}