git.delta.rocks / jrsonnet / refs/commits / dacee201fa00

difftreelog

feat(fmt) preserve comments in locals

Yaroslav Bolyukin2023-09-04parent: #016538a.patch.diff
in: master

3 files changed

modifiedcmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth
before · cmds/jrsonnet-fmt/src/children.rs
1// TODO: Return errors as trivia23use std::{fmt::Debug, marker::PhantomData, mem};45use jrsonnet_rowan_parser::{6	nodes::{Trivia, TriviaKind},7	AstNode, AstToken, SyntaxElement,8	SyntaxKind::*,9	SyntaxNode, TS,10};1112pub type ChildTrivia = Vec<Trivia>;1314/// Node should have no non-trivia tokens before element15pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {16	let mut out = Vec::new();17	for item in node.children_with_tokens() {18		if Some(&item) == end {19			break;20		}2122		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {23			out.push(trivia);24		} else if end.is_none() {25			break;26		} else {27			assert!(28				TS![, ;].contains(item.kind()) || item.kind() == ERROR,29				"silently eaten token: {:?}",30				item.kind()31			)32		}33	}34	out35}36/// Node should have no non-trivia tokens after element37pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {38	if start.is_none() {39		return Vec::new();40	}41	let mut iter = node.children_with_tokens().peekable();42	while iter.peek() != start {43		// println!("Skipped {}");44		dbg!(&iter.next());45	}46	dbg!(&iter.next());47	let mut out = Vec::new();48	for item in iter {49		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {50			out.push(trivia);51		} else {52			assert!(53				TS![, ;].contains(item.kind()) || item.kind() == ERROR,54				"silently eaten token: {:?}",55				item.kind()56			)57		}58	}59	out60}6162pub fn children_between<T: AstNode + Debug>(63	node: SyntaxNode,64	start: Option<&SyntaxElement>,65	end: Option<&SyntaxElement>,66) -> (Vec<Child<T>>, ChildTrivia) {67	let mut iter = node.children_with_tokens().peekable();68	while iter.peek() != start {69		iter.next();70	}71	iter.next();72	children(73		iter.take_while(|i| Some(i) != end),74		start.is_none() || end.is_none(),75	)76}7778pub fn should_start_with_newline(tt: &ChildTrivia) -> bool {79	// First for previous item end80	count_newlines_before(tt) >= 281}8283fn count_newlines_before(tt: &ChildTrivia) -> usize {84	let mut nl_count = 0;85	for t in tt {86		match t.kind() {87			TriviaKind::Whitespace => {88				nl_count += t.text().bytes().filter(|b| *b == b'\n').count();89			}90			_ => break,91		}92	}93	nl_count94}95fn count_newlines_after(tt: &ChildTrivia) -> usize {96	let mut nl_count = 0;97	for t in tt.iter().rev() {98		match t.kind() {99			TriviaKind::Whitespace => {100				nl_count += t.text().bytes().filter(|b| *b == b'\n').count();101			}102			TriviaKind::SingleLineHashComment => {103				nl_count += 1;104				break;105			}106			TriviaKind::SingleLineSlashComment => {107				nl_count += 1;108				break;109			}110			_ => {}111		}112	}113	nl_count114}115116pub fn children<'a, T: AstNode + Debug>(117	items: impl Iterator<Item = SyntaxElement>,118	loose: bool,119) -> (Vec<Child<T>>, ChildTrivia) {120	let mut out = Vec::new();121	let mut current_child = None::<Child<T>>;122	let mut next = ChildTrivia::new();123	// Previous element ended, do not add more inline comments124	let mut started_next = false;125	let mut had_some = false;126127	for item in items {128		if let Some(value) = item.as_node().cloned().and_then(T::cast) {129			let before_trivia = mem::take(&mut next);130			let last_child = current_child.replace(Child {131				newlines_above: if had_some {132					count_newlines_before(&before_trivia)133						+ current_child134							.as_ref()135							.map(|c| count_newlines_after(&c.inline_trivia))136							.unwrap_or_default()137				} else {138					0139				},140				before_trivia,141				value,142				inline_trivia: Vec::new(),143			});144			if let Some(last_child) = last_child {145				out.push(last_child)146			}147			had_some = true;148			started_next = false;149		} else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {150			let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment151				|| trivia.kind() == TriviaKind::SingleLineSlashComment;152			if started_next153				|| current_child.is_none()154				|| trivia.text().contains('\n') && !is_single_line_comment155			{156				next.push(trivia.clone());157				started_next = true;158			} else {159				let cur = current_child.as_mut().expect("checked not none");160				cur.inline_trivia.push(trivia);161				if is_single_line_comment {162					started_next = true;163				}164			}165			had_some = true;166		} else if loose {167			if had_some {168				break;169			}170			started_next = true;171		} else {172			assert!(173				TS![, ;].contains(item.kind()) || item.kind() == ERROR,174				"silently eaten token: {:?}",175				item.kind()176			)177		}178	}179180	if let Some(current_child) = current_child {181		out.push(current_child);182	}183184	(out, next)185}186187#[derive(Debug)]188pub struct Child<T> {189	newlines_above: usize,190	/// Comment before item, i.e191	///192	/// ```ignore193	/// // Comment194	/// item195	/// ```196	pub before_trivia: ChildTrivia,197	pub value: T,198	/// Comment after line, but located at same line199	///200	/// ```ignore201	/// item1, // Inline comment202	/// // Not inline comment203	/// item2,204	/// ```205	pub inline_trivia: ChildTrivia,206}207208impl<T> Child<T> {209	/// If this child has two newlines above in source code, so it needs to have it in output210	pub fn needs_newline_above(&self) -> bool {211		// First line for end of previous item212		self.newlines_above >= 2213	}214}
modifiedcmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/comments.rs
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -12,6 +12,7 @@
 	EndOfItems,
 }
 
+#[must_use]
 pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation) -> PrintItems {
 	let mut pi = p!(new:);
 
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -13,7 +13,7 @@
 };
 
 use crate::{
-	children::{should_start_with_newline, trivia_after},
+	children::{should_start_with_newline, trivia_after, trivia_between},
 	comments::{format_comments, CommentLocation},
 };
 
@@ -436,11 +436,45 @@
 			}
 			Expr::ExprVar(n) => p!(new: {n.name()}),
 			Expr::ExprLocal(l) => {
-				let mut pi = p!(new: str("local") >i nl);
-				for bind in l.binds() {
-					p!(pi: {bind} str(",") nl);
+				let mut pi = p!(new:);
+				let (binds, end_comments) = children_between::<Bind>(
+					l.syntax().clone(),
+					l.local_kw_token().map(Into::into).as_ref(),
+					l.semi_token().map(Into::into).as_ref(),
+				);
+				if binds.len() == 1 {
+					let bind = &binds[0];
+					p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
+					p!(pi: str("local ") {bind.value});
+				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
+				} else {
+					p!(pi: str("local") >i nl);
+					for bind in binds {
+						if bind.needs_newline_above() {
+							p!(pi: nl);
+						}
+						p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
+						p!(pi: {bind.value} str(";"));
+						p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
+					}
+					// TODO: needs_newline_above end_comments
+					p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));
+					p!(pi: <i);
 				}
-				p!(pi: <i str(";") nl {l.expr()});
+				p!(pi: str(";") nl);
+
+				let expr_comments = trivia_between(
+					l.syntax().clone(),
+					l.semi_token().map(Into::into).as_ref(),
+					l.expr()
+						.map(|e| e.syntax().clone())
+						.map(Into::into)
+						.as_ref(),
+				);
+				p!(pi: items(format_comments(&expr_comments, CommentLocation::AboveItem)));
+
+				// TODO: needs_newline_above expr
+				p!(pi: {l.expr()});
 				pi
 			}
 			Expr::ExprIfThenElse(ite) => {
@@ -515,6 +549,25 @@
 
 		local unary = !a;
 
+		local
+			//   I am comment
+			singleLocalWithItemComment = 1,
+		;
+
+		// Comment between local and expression
+
+		local
+			a = 1, //   Inline
+			// Comment above b
+			b = 4,
+
+			// c needs some space
+			c = 5,
+
+			// Comment after everything
+		;
+
+
 		local Template = {z: "foo"};
 
 		{