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

difftreelog

feat(fmt) try to use ass-stroke

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

4 files changed

modifiedcmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/Cargo.toml
+++ b/cmds/jrsonnet-fmt/Cargo.toml
@@ -8,3 +8,4 @@
 jrsonnet-rowan-parser.workspace = true
 insta = "1.15"
 indoc = "1.0"
+ass-stroke = { path = "/home/lach/build/ass-stroke/crates/ass-stroke", version = "0.1.0" }
modifiedcmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth
before · cmds/jrsonnet-fmt/src/children.rs
1// TODO: Return errors as trivia23use std::{fmt::Debug, mem};45use jrsonnet_rowan_parser::{6	nodes::{CustomError, Trivia, TriviaKind},7	AstNode, AstToken, SyntaxElement, SyntaxNode, TS,8};910pub type ChildTrivia = Vec<Result<Trivia, String>>;1112/// Node should have no non-trivia tokens before element13pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {14	let mut out = Vec::new();15	for item in node.children_with_tokens() {16		if Some(&item) == end {17			break;18		}1920		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {21			out.push(Ok(trivia));22		} else if CustomError::can_cast(item.kind()) {23			out.push(Err(item.to_string()));24		} else if end.is_none() {25			break;26		} else {27			assert!(28				TS![, ;].contains(item.kind()),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		iter.next();44	}45	iter.next();46	let mut out = Vec::new();47	for item in iter {48		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {49			out.push(Ok(trivia));50		} else if CustomError::can_cast(item.kind()) {51			out.push(Err(item.to_string()))52		} else {53			assert!(54				TS![, ;].contains(item.kind()),55				"silently eaten token: {:?}",56				item.kind()57			)58		}59	}60	out61}6263pub fn trivia_between(64	node: SyntaxNode,65	start: Option<&SyntaxElement>,66	end: Option<&SyntaxElement>,67) -> EndingComments {68	let mut iter = node.children_with_tokens().peekable();69	while iter.peek() != start {70		iter.next();71	}72	iter.next();7374	let loose = start.is_none() || end.is_none();7576	let mut out = Vec::new();77	for item in iter.take_while(|i| Some(i) != end) {78		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {79			out.push(Ok(trivia));80		} else if CustomError::can_cast(item.kind()) {81			out.push(Err(item.to_string()))82		} else if loose {83			break;84		} else {85			assert!(86				TS![, ;].contains(item.kind()),87				"silently eaten token: {:?}",88				item.kind()89			)90		}91	}92	EndingComments {93		should_start_with_newline: should_start_with_newline(None, &out),94		trivia: out,95	}96}9798pub fn children_between<T: AstNode + Debug>(99	node: SyntaxNode,100	start: Option<&SyntaxElement>,101	end: Option<&SyntaxElement>,102) -> (Vec<Child<T>>, EndingComments) {103	let mut iter = node.children_with_tokens().peekable();104	while iter.peek() != start {105		iter.next();106	}107	iter.next();108	children(109		iter.take_while(|i| Some(i) != end),110		start.is_none() || end.is_none(),111	)112}113114pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {115	count_newlines_before(tt)116		+ prev_inline117			.map(count_newlines_after)118			.unwrap_or_default()119120		// First for previous item end, second for current item121		>= 2122}123124fn count_newlines_before(tt: &ChildTrivia) -> usize {125	let mut nl_count = 0;126	for t in tt {127		match t {128			Ok(t) => match t.kind() {129				TriviaKind::Whitespace => {130					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();131				}132				_ => break,133			},134			Err(_) => {135				nl_count += 1;136			}137		}138	}139	nl_count140}141fn count_newlines_after(tt: &ChildTrivia) -> usize {142	let mut nl_count = 0;143	for t in tt.iter().rev() {144		match t {145			Ok(t) => match t.kind() {146				TriviaKind::Whitespace => {147					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();148				}149				TriviaKind::SingleLineHashComment => {150					nl_count += 1;151					break;152				}153				TriviaKind::SingleLineSlashComment => {154					nl_count += 1;155					break;156				}157				_ => {}158			},159			Err(_) => nl_count += 1,160		}161	}162	nl_count163}164165pub fn children<T: AstNode + Debug>(166	items: impl Iterator<Item = SyntaxElement>,167	loose: bool,168) -> (Vec<Child<T>>, EndingComments) {169	let mut out = Vec::new();170	let mut current_child = None::<Child<T>>;171	let mut next = ChildTrivia::new();172	// Previous element ended, do not add more inline comments173	let mut started_next = false;174	let mut had_some = false;175176	for item in items {177		if let Some(value) = item.as_node().cloned().and_then(T::cast) {178			let before_trivia = mem::take(&mut next);179			let last_child = current_child.replace(Child {180				// First item should not start with newline181				should_start_with_newline: had_some182					&& should_start_with_newline(183						current_child.as_ref().map(|c| &c.inline_trivia),184						&before_trivia,185					),186				before_trivia,187				value,188				inline_trivia: Vec::new(),189			});190			if let Some(last_child) = last_child {191				out.push(last_child)192			}193			had_some = true;194			started_next = false;195		} else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {196			let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment197				|| trivia.kind() == TriviaKind::SingleLineSlashComment;198			if started_next199				|| current_child.is_none()200				|| trivia.text().contains('\n') && !is_single_line_comment201			{202				next.push(Ok(trivia.clone()));203				started_next = true;204			} else {205				let cur = current_child.as_mut().expect("checked not none");206				cur.inline_trivia.push(Ok(trivia));207				if is_single_line_comment {208					started_next = true;209				}210			}211			had_some = true;212		} else if CustomError::can_cast(item.kind()) {213			next.push(Err(item.to_string()))214		} else if loose {215			if had_some {216				break;217			}218			started_next = true;219		} else {220			assert!(221				TS![, ;].contains(item.kind()),222				"silently eaten token: {:?}",223				item.kind()224			)225		}226	}227228	let ending_comments = EndingComments {229		should_start_with_newline: should_start_with_newline(230			current_child.as_ref().map(|c| &c.inline_trivia),231			&next,232		),233		trivia: next,234	};235236	if let Some(current_child) = current_child {237		out.push(current_child);238	}239240	(out, ending_comments)241}242243#[derive(Debug)]244pub struct Child<T> {245	/// If this child has two newlines above in source code, so it needs to have it in the output246	pub should_start_with_newline: bool,247	/// Comment before item, i.e248	///249	/// ```ignore250	/// // Comment251	/// item252	/// ```253	pub before_trivia: ChildTrivia,254	pub value: T,255	/// Comment after line, but located at same line256	///257	/// ```ignore258	/// item1, // Inline comment259	/// // Not inline comment260	/// item2,261	/// ```262	pub inline_trivia: ChildTrivia,263}264265pub struct EndingComments {266	/// If this child has two newlines above in source code, so it needs to have it in the output267	pub should_start_with_newline: bool,268	pub trivia: ChildTrivia,269}
after · cmds/jrsonnet-fmt/src/children.rs
1// TODO: Return errors as trivia23use std::{fmt::Debug, mem};45use jrsonnet_rowan_parser::{6	nodes::{CustomError, Trivia, TriviaKind},7	AstNode, AstToken, SyntaxElement, SyntaxNode, TS,8};910pub type ChildTrivia = Vec<Result<Trivia, String>>;1112/// Node should have no non-trivia tokens before element13pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {14	let mut out = Vec::new();15	for item in node.children_with_tokens() {16		if Some(&item) == end {17			break;18		}1920		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {21			out.push(Ok(trivia));22		} else if CustomError::can_cast(item.kind()) {23			out.push(Err(item.to_string()));24		} else if end.is_none() {25			break;26		} else {27			assert!(28				TS![, ;].contains(item.kind()),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		iter.next();44	}45	iter.next();46	let mut out = Vec::new();47	for item in iter {48		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {49			out.push(Ok(trivia));50		} else if CustomError::can_cast(item.kind()) {51			out.push(Err(item.to_string()))52		} else {53			assert!(54				TS![, ;].contains(item.kind()),55				"silently eaten token: {:?}",56				item.kind()57			)58		}59	}60	out61}6263pub fn trivia_between(64	node: SyntaxNode,65	start: Option<&SyntaxElement>,66	end: Option<&SyntaxElement>,67) -> EndingComments {68	let mut iter = node.children_with_tokens().peekable();69	while iter.peek() != start {70		iter.next();71	}72	iter.next();7374	let loose = start.is_none() || end.is_none();7576	let mut out = Vec::new();77	for item in iter.take_while(|i| Some(i) != end) {78		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {79			out.push(Ok(trivia));80		} else if CustomError::can_cast(item.kind()) {81			out.push(Err(item.to_string()))82		} else if loose {83			break;84		} else {85			assert!(86				TS![, ;].contains(item.kind()),87				"silently eaten token: {:?}",88				item.kind()89			)90		}91	}92	EndingComments {93		should_start_with_newline: should_start_with_newline(None, &out),94		trivia: out,95	}96}9798pub fn children_between<T: AstNode + Debug>(99	node: SyntaxNode,100	start: Option<&SyntaxElement>,101	end: Option<&SyntaxElement>,102) -> (Vec<Child<T>>, EndingComments) {103	let mut iter = node.children_with_tokens().peekable();104	while iter.peek() != start {105		iter.next();106	}107	iter.next();108	children(109		iter.take_while(|i| Some(i) != end),110		start.is_none() || end.is_none(),111	)112}113114pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {115	count_newlines_before(tt)116		+ prev_inline117			.map(count_newlines_after)118			.unwrap_or_default()119120		// First for previous item end, second for current item121		>= 2122}123124fn count_newlines_before(tt: &ChildTrivia) -> usize {125	let mut nl_count = 0;126	for t in tt {127		match t {128			Ok(t) => match t.kind() {129				TriviaKind::Whitespace => {130					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();131				}132				_ => break,133			},134			Err(_) => {135				nl_count += 1;136			}137		}138	}139	nl_count140}141fn count_newlines_after(tt: &ChildTrivia) -> usize {142	let mut nl_count = 0;143	for t in tt.iter().rev() {144		match t {145			Ok(t) => match t.kind() {146				TriviaKind::Whitespace => {147					nl_count += t.text().bytes().filter(|b| *b == b'\n').count();148				}149				TriviaKind::SingleLineHashComment => {150					nl_count += 1;151					break;152				}153				TriviaKind::SingleLineSlashComment => {154					nl_count += 1;155					break;156				}157				_ => {}158			},159			Err(_) => nl_count += 1,160		}161	}162	nl_count163}164165pub fn children<T: AstNode + Debug>(166	items: impl Iterator<Item = SyntaxElement>,167	loose: bool,168) -> (Vec<Child<T>>, EndingComments) {169	let mut out = Vec::new();170	let mut current_child = None::<Child<T>>;171	let mut next = ChildTrivia::new();172	// Previous element ended, do not add more inline comments173	let mut started_next = false;174	let mut had_some = false;175176	for item in items {177		if let Some(value) = item.as_node().cloned().and_then(T::cast) {178			let before_trivia = mem::take(&mut next);179			let last_child = current_child.replace(Child {180				// First item should not start with newline181				should_start_with_newline: had_some182					&& should_start_with_newline(183						current_child.as_ref().map(|c| &c.inline_trivia),184						&before_trivia,185					),186				before_trivia,187				value,188				inline_trivia: Vec::new(),189			});190			if let Some(last_child) = last_child {191				out.push(last_child)192			}193			had_some = true;194			started_next = false;195		} else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {196			let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment197				|| trivia.kind() == TriviaKind::SingleLineSlashComment;198			if started_next199				|| current_child.is_none()200				|| trivia.text().contains('\n') && !is_single_line_comment201			{202				next.push(Ok(trivia.clone()));203				started_next = true;204			} else {205				let cur = current_child.as_mut().expect("checked not none");206				cur.inline_trivia.push(Ok(trivia));207				if is_single_line_comment {208					started_next = true;209				}210			}211			had_some = true;212		} else if CustomError::can_cast(item.kind()) {213			next.push(Err(item.to_string()))214		} else if loose {215			if had_some {216				break;217			}218			started_next = true;219		} else {220			assert!(221				TS![, ;].contains(item.kind()),222				"silently eaten token: {:?}",223				item.kind()224			)225		}226	}227228	let ending_comments = EndingComments {229		should_start_with_newline: should_start_with_newline(230			current_child.as_ref().map(|c| &c.inline_trivia),231			&next,232		),233		trivia: next,234	};235236	if let Some(current_child) = current_child {237		out.push(current_child);238	}239240	(out, ending_comments)241}242243#[derive(Debug)]244pub struct Child<T> {245	/// If this child has two newlines above in source code, so it needs to have it in the output246	pub should_start_with_newline: bool,247	/// Comment before item, i.e248	///249	/// ```ignore250	/// // Comment251	/// item252	/// ```253	pub before_trivia: ChildTrivia,254	pub value: T,255	/// Comment after line, but located at same line256	///257	/// ```ignore258	/// item1, // Inline comment259	/// // Not inline comment260	/// item2,261	/// ```262	pub inline_trivia: ChildTrivia,263}264265pub struct EndingComments {266	/// If this child has two newlines above in source code, so it needs to have it in the output267	pub should_start_with_newline: bool,268	pub trivia: ChildTrivia,269}270impl EndingComments {271	pub fn is_empty(&self) -> bool {272		!self.should_start_with_newline && self.trivia.is_empty()273	}274}
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -296,7 +296,6 @@
 	fn print(&self) -> PrintItems {
 		match self {
 			ObjBody::ObjBodyComp(l) => {
-				let mut pi = p!(new: str("{") >i nl);
 				let (children, end_comments) = children_between::<Member>(
 					l.syntax().clone(),
 					l.l_brace_token().map(Into::into).as_ref(),
@@ -309,6 +308,7 @@
 						.into(),
 					),
 				);
+				let mut pi = p!(new: str("{") >i nl);
 				for mem in children.into_iter() {
 					if mem.should_start_with_newline {
 						p!(pi: nl);
@@ -341,7 +341,6 @@
 					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));
 					p!(pi: {mem.value});
 					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));
-					p!(pi: nl)
 				}
 				if end_comments.should_start_with_newline {
 					p!(pi: nl);
@@ -352,12 +351,15 @@
 				pi
 			}
 			ObjBody::ObjBodyMemberList(l) => {
-				let mut pi = p!(new: str("{") >i nl);
 				let (children, end_comments) = children_between::<Member>(
 					l.syntax().clone(),
 					l.l_brace_token().map(Into::into).as_ref(),
 					l.r_brace_token().map(Into::into).as_ref(),
 				);
+				if children.is_empty() && end_comments.is_empty() {
+					return p!(new: str("{ }"));
+				}
+				let mut pi = p!(new: str("{") >i nl);
 				for mem in children.into_iter() {
 					if mem.should_start_with_newline {
 						p!(pi: nl);
@@ -395,7 +397,7 @@
 				p!(new: {d.into()} str(" = ") {d.value()})
 			}
 			Bind::BindFunction(f) => {
-				p!(new: str("function") {f.params()} str(" = ") {f.value()})
+				p!(new: {f.name()} {f.params()} str(" = ") {f.value()})
 			}
 		}
 	}
@@ -504,7 +506,7 @@
 							p!(pi: nl);
 						}
 						p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
-						p!(pi: {bind.value} str(";"));
+						p!(pi: {bind.value} str(","));
 						p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
 					}
 					if end_comments.should_start_with_newline {
@@ -573,16 +575,45 @@
 	}
 }
 
+fn format(input: &str) -> String {
+	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
+	if !errors.is_empty() {
+		let mut builder = ass_stroke::SnippetBuilder::new(input);
+		for error in errors {
+			builder
+				.error(ass_stroke::Text::single(
+					format!("{:?}", error.error).chars(),
+					Default::default(),
+				))
+				.range(
+					error.range.start().into()
+						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),
+				)
+				.build();
+		}
+		let snippet = builder.build();
+		let ansi = ass_stroke::source_to_ansi(&snippet);
+		println!("{ansi}");
+	}
+	dprint_core::formatting::format(
+		|| parsed.print(),
+		PrintOptions {
+			indent_width: 2,
+			max_width: 100,
+			use_tabs: false,
+			new_line_text: "\n",
+		},
+	)
+}
 fn main() {
-	let (parsed, _errors) = jrsonnet_rowan_parser::parse(
-		r#"
+	let input = r#"
 
 
 		# Edit me!
 		local b = import "b.libsonnet";  # comment
 		local a = import "a.libsonnet";
 
-			 local f(x,y)=x+y;
+		local f(x,y)=x+y;
 
 		local {a: [b, ..., c], d, ...e} = null;
 
@@ -693,28 +724,29 @@
 		  else Template {},
 
 		  compspecs: {
-			obj_with_no_item: {for i in [1, 2, 3]},
-			obj_with_2_items: {a:1, b:2, for i in [1,2,3]},
+			obj_with_no_item: {a:1, for i in [1, 2, 3]},
+			obj_with_2_items: {a:1, /*b:2,*/ for i in [1,2,3]},
 		  }
 
 		} + Template
+"#;
 
-
-		// Comment after everything
-"#,
-	);
-
-	// dbg!(errors);
-	dbg!(&parsed);
-
-	let o = dprint_core::formatting::format(
-		|| parsed.print(),
-		PrintOptions {
-			indent_width: 2,
-			max_width: 100,
-			use_tabs: false,
-			new_line_text: "\n",
-		},
-	);
-	println!("{}", o);
+	let mut iteration = 0;
+	let mut a = input.to_string();
+	let mut b;
+	// https://github.com/dprint/dprint/pull/423
+	loop {
+		b = format(&a).trim().to_owned();
+		if a == b {
+			break;
+		}
+		println!("{b}");
+		a = b;
+		iteration += 1;
+		if iteration > 5 {
+			panic!("formatting not converged");
+			break;
+		}
+	}
+	println!("{a}");
 }
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -12,10 +12,7 @@
 # Use mimalloc as allocator
 mimalloc = ["mimallocator"]
 # Experimental feature, which allows to preserve order of object fields
-exp-preserve-order = [
-    "jrsonnet-evaluator/exp-preserve-order",
-    "jrsonnet-cli/exp-preserve-order",
-]
+exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order", "jrsonnet-cli/exp-preserve-order"]
 # Destructuring of locals
 exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
 # Iteration over objects yields [key, value] elements
@@ -44,3 +41,4 @@
 clap_complete = { version = "4.1" }
 serde_json = "1.0.104"
 serde = { workspace = true, features = ["derive"] }
+ass-stroke = { git = "https://github.com/CertainLach/ass-stroke", version = "0.1.0" }