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}1415#[must_use]16pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation) -> PrintItems {17 let mut pi = p!(new:);1819 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(|c| c == '\n' || c == '\t').unwrap_or(text.len());24 let sliced = &text[..pos];25 p!(pi: string(sliced.to_string()));26 text = &text[pos..];27 if !text.is_empty() {28 match text.as_bytes()[0] {29 b'\n' => p!(pi: nl),30 b'\t' => p!(pi: 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().map(|l| l.is_empty()).unwrap_or(false) {69 lines.pop();70 }71 if lines.len() == 1 && !doc {72 if matches!(loc, CommentLocation::ItemInline) {73 p!(pi: str(" "));74 }75 p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */"))76 } else if !lines.is_empty() {77 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {78 let offset = a79 .bytes()80 .zip(b.bytes())81 .take_while(|(a, b)| a == b && (a.is_ascii_whitespace() || *a == b'*'))82 .count();83 &a[..offset]84 }85 86 let mut common_ws_padding = (if immediate_start && lines.len() > 1 {87 common_ws_prefix(&lines[1], &lines[1])88 } else {89 common_ws_prefix(&lines[0], &lines[0])90 })91 .to_string();92 for line in lines93 .iter()94 .skip(if immediate_start { 2 } else { 1 })95 .filter(|l| !l.is_empty())96 {97 common_ws_padding = common_ws_prefix(&common_ws_padding, line).to_string();98 }99 for line in lines100 .iter_mut()101 .skip(if immediate_start { 1 } else { 0 })102 .filter(|l| !l.is_empty())103 {104 *line = line105 .strip_prefix(&common_ws_padding)106 .expect("all non-empty lines start with this padding")107 .to_string();108 }109110 p!(pi: str("/*"));111 if doc {112 p!(pi: str("*"));113 }114 p!(pi: nl);115 for mut line in lines {116 if doc {117 p!(pi: str(" *"));118 }119 if line.is_empty() {120 p!(pi: nl);121 } else {122 if doc {123 p!(pi: str(" "));124 }125 while let Some(new_line) = line.strip_prefix('\t') {126 if doc {127 p!(pi: str(" "));128 } else {129 p!(pi: tab);130 }131 line = new_line.to_string();132 }133 p!(pi: string(line.to_string()) nl)134 }135 }136 if doc {137 p!(pi: str(" "));138 }139 p!(pi: str("*/") nl)140 }141 }142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 TriviaKind::SingleLineHashComment => {159 if matches!(loc, CommentLocation::ItemInline) {160 p!(pi: str(" "))161 }162 p!(pi: str("# ") string(c.text().strip_prefix('#').expect("hash comment starts with #").trim().to_string()));163 if !matches!(loc, CommentLocation::ItemInline) {164 p!(pi: nl)165 }166 }167 TriviaKind::SingleLineSlashComment => {168 if matches!(loc, CommentLocation::ItemInline) {169 p!(pi: str(" "))170 }171 p!(pi: str("// ") string(c.text().strip_prefix("//").expect("comment starts with //").trim().to_string()));172 if !matches!(loc, CommentLocation::ItemInline) {173 p!(pi: nl)174 }175 }176 177 TriviaKind::ErrorCommentTooShort => p!(pi: str("/*/")),178 TriviaKind::ErrorCommentUnterminated => p!(pi: string(c.text().to_string())),179 }180 }181182 pi183}