1use dprint_core::formatting::PrintItems;2use jrsonnet_rowan_parser::{nodes::TriviaKind, AstToken};34use crate::{children::ChildTrivia, p, pi};56pub enum CommentLocation {7 8 AboveItem,9 10 ItemInline,11 12 EndOfItems,13}1415pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation, out: &mut PrintItems) {16 for c in comments {17 let Ok(c) = c else {18 let mut text = c.as_ref().unwrap_err() as &str;19 while !text.is_empty() {20 let pos = text.find(|c| c == '\n' || c == '\t').unwrap_or(text.len());21 let sliced = &text[..pos];22 p!(out, string(sliced.to_string()));23 text = &text[pos..];24 if !text.is_empty() {25 match text.as_bytes()[0] {26 b'\n' => p!(out, nl),27 b'\t' => p!(out, tab),28 _ => unreachable!(),29 }30 text = &text[1..];31 }32 }33 continue;34 };35 match c.kind() {36 TriviaKind::Whitespace => {}37 TriviaKind::MultiLineComment => {38 let mut text = c39 .text()40 .strip_prefix("/*")41 .expect("ml comment starts with /*")42 .strip_suffix("*/")43 .expect("ml comment ends with */");44 45 let doc = if text.starts_with('*') {46 text = &text[1..];47 true48 } else {49 false50 };51 52 let mut immediate_start = true;53 let mut lines = text54 .split('\n')55 .map(|l| l.trim_end().to_string())56 .skip_while(|l| {57 if l.is_empty() {58 immediate_start = false;59 true60 } else {61 false62 }63 })64 .collect::<Vec<_>>();65 while lines.last().map(|l| l.is_empty()).unwrap_or(false) {66 lines.pop();67 }68 if lines.len() == 1 && !doc {69 if matches!(loc, CommentLocation::ItemInline) {70 p!(out, str(" "));71 }72 p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl)73 } else if !lines.is_empty() {74 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {75 let offset = a76 .bytes()77 .zip(b.bytes())78 .take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))79 .count();80 &a[..offset]81 }82 83 let mut common_ws_padding = (if immediate_start && lines.len() > 1 {84 common_ws_prefix(&lines[1], &lines[1])85 } else {86 common_ws_prefix(&lines[0], &lines[0])87 })88 .to_string();89 for line in lines90 .iter()91 .skip(if immediate_start { 2 } else { 1 })92 .filter(|l| !l.is_empty())93 {94 common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();95 }96 for line in lines97 .iter_mut()98 .skip(if immediate_start { 1 } else { 0 })99 .filter(|l| !l.is_empty())100 {101 *line = line102 .strip_prefix(&common_ws_padding)103 .expect("all non-empty lines start with this padding")104 .to_string();105 }106107 p!(out, str("/*"));108 if doc {109 p!(out, str("*"));110 }111 p!(out, nl);112 for mut line in lines {113 if doc {114 p!(out, str(" *"));115 }116 if line.is_empty() {117 p!(out, nl);118 } else {119 if doc {120 p!(out, str(" "));121 }122 while let Some(new_line) = line.strip_prefix('\t') {123 if doc {124 p!(out, str(" "));125 } else {126 p!(out, tab);127 }128 line = new_line.to_string();129 }130 p!(out, string(line.to_string()) nl)131 }132 }133 if doc {134 p!(out, str(" "));135 }136 p!(out, str("*/") nl)137 }138 }139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 TriviaKind::SingleLineHashComment => {156 if matches!(loc, CommentLocation::ItemInline) {157 p!(out, str(" "))158 }159 p!(out, str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));160 if !matches!(loc, CommentLocation::ItemInline) {161 p!(out, nl)162 }163 }164 TriviaKind::SingleLineSlashComment => {165 if matches!(loc, CommentLocation::ItemInline) {166 p!(out, str(" "))167 }168 p!(out, str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));169 if !matches!(loc, CommentLocation::ItemInline) {170 p!(out, nl)171 }172 }173 174 TriviaKind::ErrorCommentTooShort => p!(out, str("/*/")),175 TriviaKind::ErrorCommentUnterminated => p!(out, string(c.text().to_string())),176 }177 }178}