git.delta.rocks / jrsonnet / refs/commits / 5858c9313e03

difftreelog

source

crates/jrsonnet-formatter/src/comments.rs4.8 KiBsourcehistory
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	/// Above local, field, other things10	AboveItem,11	/// After item12	ItemInline,13	/// After all items in object14	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				// doc-style comment, /**48				let doc = if text.starts_with('*') {49					text = &text[1..];50					true51				} else {52					false53				};54				// Is comment starts with text immediatly, i.e /*text55				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					// First line is not empty, extract ws prefix of it92					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			// TODO: Keep common padding for multiple continous lines of single-line comments149			// I.e150			// ```151			// #  Line1152			// #    Line2153			// ```154			// Should be reformatted as155			// ```156			// # Line1157			// #   Line2158			// ```159			// But currently comment formatter is not aware of continous comment lines, and reformats it as160			// ```161			// # Line1162			// # Line2163			// ```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			// Garbage in - garbage out183			TriviaKind::ErrorCommentTooShort => p!(out, str("/*/")),184			TriviaKind::ErrorCommentUnterminated => p!(out, string(c.text().to_string())),185		}186	}187}