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
--- a/cmds/jrsonnet-fmt/src/children.rs
+++ b/cmds/jrsonnet-fmt/src/children.rs
@@ -40,10 +40,9 @@
 	}
 	let mut iter = node.children_with_tokens().peekable();
 	while iter.peek() != start {
-		// println!("Skipped {}");
-		dbg!(&iter.next());
+		iter.next();
 	}
-	dbg!(&iter.next());
+	iter.next();
 	let mut out = Vec::new();
 	for item in iter {
 		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
@@ -59,6 +58,36 @@
 	out
 }
 
+pub fn trivia_between(
+	node: SyntaxNode,
+	start: Option<&SyntaxElement>,
+	end: Option<&SyntaxElement>,
+) -> ChildTrivia {
+	let mut iter = node.children_with_tokens().peekable();
+	while iter.peek() != start {
+		iter.next();
+	}
+	iter.next();
+
+	let loose = start.is_none() || end.is_none();
+
+	let mut out = Vec::new();
+	for item in iter.take_while(|i| Some(i) != end) {
+		if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+			out.push(trivia);
+		} else if loose {
+			break;
+		} else {
+			assert!(
+				TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+				"silently eaten token: {:?}",
+				item.kind()
+			)
+		}
+	}
+	out
+}
+
 pub fn children_between<T: AstNode + Debug>(
 	node: SyntaxNode,
 	start: Option<&SyntaxElement>,
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
after · cmds/jrsonnet-fmt/src/main.rs
1use std::any::type_name;23use children::{children_between, trivia_before};4use dprint_core::formatting::{PrintItems, PrintOptions};5use jrsonnet_rowan_parser::{6	nodes::{7		ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,8		DestructRest, Expr, Field, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal,9		Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text,10		UnaryOperator,11	},12	AstNode, AstToken, SyntaxToken,13};1415use crate::{16	children::{should_start_with_newline, trivia_after, trivia_between},17	comments::{format_comments, CommentLocation},18};1920mod children;21mod comments;22#[cfg(test)]23mod tests;2425pub trait Printable {26	fn print(&self) -> PrintItems;27}2829macro_rules! pi {30	(@i; $($t:tt)*) => {{31		#[allow(unused_mut)]32		let mut o = dprint_core::formatting::PrintItems::new();33		pi!(@s; o: $($t)*);34		o35	}};36	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{37		$o.push_str($e);38		pi!(@s; $o: $($t)*);39	}};40	(@s; $o:ident: nl $($t:tt)*) => {{41		$o.push_signal(dprint_core::formatting::Signal::NewLine);42		pi!(@s; $o: $($t)*);43	}};44	(@s; $o:ident: tab $($t:tt)*) => {{45		$o.push_signal(dprint_core::formatting::Signal::Tab);46		pi!(@s; $o: $($t)*);47	}};48	(@s; $o:ident: >i $($t:tt)*) => {{49		$o.push_signal(dprint_core::formatting::Signal::StartIndent);50		pi!(@s; $o: $($t)*);51	}};52	(@s; $o:ident: <i $($t:tt)*) => {{53		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);54		pi!(@s; $o: $($t)*);55	}};56	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{57		$o.extend($expr.print());58		pi!(@s; $o: $($t)*);59	}};60	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{61		$o.extend($expr);62		pi!(@s; $o: $($t)*);63	}};64	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{65		if $e {66			pi!(@s; $o: $($then)*);67		}68		pi!(@s; $o: $($t)*);69	}};70	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{71		if $e {72			pi!(@s; $o: $($then)*);73		} else {74			pi!(@s; $o: $($else)*);75		}76		pi!(@s; $o: $($t)*);77	}};78	(@s; $i:ident:) => {}79}80macro_rules! p {81	(new: $($t:tt)*) => {82		pi!(@i; $($t)*)83	};84	($o:ident: $($t:tt)*) => {85		pi!(@s; $o: $($t)*)86	};87}88pub(crate) use p;89pub(crate) use pi;9091impl<P> Printable for Option<P>92where93	P: Printable,94{95	fn print(&self) -> PrintItems {96		if let Some(v) = self {97			v.print()98		} else {99			p!(new: str(100				&format!(101					"/*missing {}*/",102					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")103				),104			))105		}106	}107}108109impl Printable for SyntaxToken {110	fn print(&self) -> PrintItems {111		p!(new: str(&self.to_string()))112	}113}114115impl Printable for Text {116	fn print(&self) -> PrintItems {117		p!(new: str(&format!("{}", self)))118	}119}120impl Printable for Number {121	fn print(&self) -> PrintItems {122		p!(new: str(&format!("{}", self)))123	}124}125126impl Printable for Name {127	fn print(&self) -> PrintItems {128		p!(new: {self.ident_lit()})129	}130}131132impl Printable for DestructRest {133	fn print(&self) -> PrintItems {134		let mut pi = p!(new: str("..."));135		if let Some(name) = self.into() {136			p!(pi: {name});137		}138		pi139	}140}141142impl Printable for Destruct {143	fn print(&self) -> PrintItems {144		let mut pi = p!(new:);145		match self {146			Destruct::DestructFull(f) => {147				p!(pi: {f.name()})148			}149			Destruct::DestructSkip(_) => p!(pi: str("?")),150			Destruct::DestructArray(a) => {151				p!(pi: str("[") >i nl);152				for el in a.destruct_array_parts() {153					match el {154						DestructArrayPart::DestructArrayElement(e) => {155							p!(pi: {e.destruct()} str(",") nl)156						}157						DestructArrayPart::DestructRest(d) => {158							p!(pi: {d} str(",") nl)159						}160					}161				}162				p!(pi: <i str("]"));163			}164			Destruct::DestructObject(o) => {165				p!(pi: str("{") >i nl);166				for item in o.destruct_object_fields() {167					p!(pi: {item.field()});168					if let Some(des) = item.destruct() {169						p!(pi: str(": ") {des})170					}171					if let Some(def) = item.expr() {172						p!(pi: str(" = ") {def});173					}174					p!(pi: str(",") nl);175				}176				if let Some(rest) = o.destruct_rest() {177					p!(pi: {rest} nl)178				}179				p!(pi: <i str("}"));180			}181		}182		pi183	}184}185186impl Printable for FieldName {187	fn print(&self) -> PrintItems {188		match self {189			FieldName::FieldNameFixed(f) => {190				if let Some(id) = f.id() {191					p!(new: {id})192				} else if let Some(str) = f.text() {193					p!(new: {str})194				} else {195					p!(new: str("/*missing FieldName*/"))196				}197			}198			FieldName::FieldNameDynamic(d) => {199				p!(new: str("[") {d.expr()} str("]"))200			}201		}202	}203}204impl Printable for Field {205	fn print(&self) -> PrintItems {206		let mut pi = p!(new:);207		match self {208			Field::FieldNormal(n) => {209				p!(pi: {n.field_name()});210				if n.plus_token().is_some() {211					p!(pi: str("+"));212				}213				p!(pi: str(": ") {n.expr()});214			}215			Field::FieldMethod(m) => {216				p!(pi: {m.field_name()} {m.params_desc()} str(": ") {m.expr()});217			}218		}219		pi220	}221}222223impl Printable for ObjLocal {224	fn print(&self) -> PrintItems {225		p!(new: str("local ") {self.bind()})226	}227}228229impl Printable for Assertion {230	fn print(&self) -> PrintItems {231		let mut pi = p!(new: str("assert ") {self.condition()});232		if self.colon_token().is_some() || self.message().is_some() {233			p!(pi: str(": ") {self.message()})234		}235		pi236	}237}238239impl Printable for ParamsDesc {240	fn print(&self) -> PrintItems {241		let mut pi = p!(new: str("(") >i nl);242		for param in self.params() {243			p!(pi: {param.destruct()});244			if param.assign_token().is_some() || param.expr().is_some() {245				p!(pi: str(" = ") {param.expr()})246			}247			p!(pi: str(",") nl)248		}249		p!(pi: <i str(")"));250		pi251	}252}253impl Printable for ArgsDesc {254	fn print(&self) -> PrintItems {255		let mut pi = p!(new: str("(") >i nl);256		for arg in self.args() {257			if arg.name().is_some() || arg.assign_token().is_some() {258				p!(pi: {arg.name()} str(" = "));259			}260			p!(pi: {arg.expr()} str(",") nl)261		}262		p!(pi: <i str(")"));263		pi264	}265}266impl Printable for SliceDesc {267	fn print(&self) -> PrintItems {268		let mut pi = p!(new: str("["));269		if self.from().is_some() {270			p!(pi: {self.from()});271		}272		p!(pi: str(":"));273		if self.end().is_some() {274			p!(pi: {self.end().map(|e|e.expr())})275		}276		// Keep only one : in case if we don't need step277		if self.step().is_some() {278			p!(pi: str(":") {self.step().map(|e|e.expr())});279		}280		p!(pi: str("]"));281		pi282	}283}284285impl Printable for ObjBody {286	fn print(&self) -> PrintItems {287		match self {288			ObjBody::ObjBodyComp(_) => todo!(),289			ObjBody::ObjBodyMemberList(l) => {290				let mut pi = p!(new: str("{") >i nl);291				let (children, end_comments) = children_between::<Member>(292					l.syntax().clone(),293					l.l_brace_token().map(Into::into).as_ref(),294					l.r_brace_token().map(Into::into).as_ref(),295				);296				for mem in children.into_iter() {297					if mem.needs_newline_above() {298						p!(pi: nl);299					}300					p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));301					match mem.value {302						Member::MemberBindStmt(b) => {303							p!(pi: {b.obj_local()})304						}305						Member::MemberAssertStmt(ass) => {306							p!(pi: {ass.assertion()})307						}308						Member::MemberField(f) => {309							p!(pi: {f.field()})310						}311					}312					p!(pi: str(","));313					p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));314					p!(pi: nl)315				}316317				// TODO: implement same thing as needs_newline_above, but for end comments318				if should_start_with_newline(&end_comments) {319					p!(pi: nl);320				}321				p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));322				p!(pi: <i str("}"));323				pi324			}325		}326	}327}328impl Printable for UnaryOperator {329	fn print(&self) -> PrintItems {330		p!(new: str(self.text()))331	}332}333impl Printable for BinaryOperator {334	fn print(&self) -> PrintItems {335		p!(new: str(self.text()))336	}337}338impl Printable for Bind {339	fn print(&self) -> PrintItems {340		match self {341			Bind::BindDestruct(d) => {342				p!(new: {d.into()} str(" = ") {d.value()})343			}344			Bind::BindFunction(f) => {345				p!(new: str("function") {f.params()} str(" = ") {f.value()})346			}347		}348	}349}350impl Printable for Literal {351	fn print(&self) -> PrintItems {352		p!(new: str(&self.syntax().to_string()))353	}354}355impl Printable for ImportKind {356	fn print(&self) -> PrintItems {357		p!(new: str(&self.syntax().to_string()))358	}359}360impl Printable for LhsExpr {361	fn print(&self) -> PrintItems {362		p!(new: {self.expr()})363	}364}365impl Printable for ForSpec {366	fn print(&self) -> PrintItems {367		p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})368	}369}370impl Printable for IfSpec {371	fn print(&self) -> PrintItems {372		p!(new: str("if ") {self.expr()})373	}374}375impl Printable for CompSpec {376	fn print(&self) -> PrintItems {377		match self {378			CompSpec::ForSpec(f) => f.print(),379			CompSpec::IfSpec(i) => i.print(),380		}381	}382}383impl Printable for Expr {384	fn print(&self) -> PrintItems {385		match self {386			Expr::ExprBinary(b) => {387				p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})388			}389			Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),390			Expr::ExprSlice(s) => {391				p!(new: {s.expr()} {s.slice_desc()})392			}393			Expr::ExprIndex(i) => {394				p!(new: {i.expr()} str(".") {i.index()})395			}396			Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),397			Expr::ExprApply(a) => {398				let mut pi = p!(new: {a.expr()} {a.args_desc()});399				if a.tailstrict_kw_token().is_some() {400					p!(pi: str(" tailstrict"));401				}402				pi403			}404			Expr::ExprObjExtend(ex) => {405				p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})406			}407			Expr::ExprParened(p) => {408				p!(new: str("(") {p.expr()} str(")"))409			}410			Expr::ExprIntrinsicThisFile(_) => p!(new: str("$intrinsicThisFile")),411			Expr::ExprIntrinsicId(_) => p!(new: str("$intrinsicId")),412			Expr::ExprIntrinsic(i) => p!(new: str("$intrinsic(") {i.name()} str(")")),413			Expr::ExprString(s) => p!(new: {s.text()}),414			Expr::ExprNumber(n) => p!(new: {n.number()}),415			Expr::ExprArray(a) => {416				let mut pi = p!(new: str("[") >i nl);417				for el in a.exprs() {418					p!(pi: {el} str(",") nl);419				}420				p!(pi: <i str("]"));421				pi422			}423			Expr::ExprObject(o) => {424				p!(new: {o.obj_body()})425			}426			Expr::ExprArrayComp(arr) => {427				let mut pi = p!(new: str("[") {arr.expr()});428				for spec in arr.comp_specs() {429					p!(pi: str(" ") {spec});430				}431				p!(pi: str("]"));432				pi433			}434			Expr::ExprImport(v) => {435				p!(new: {v.import_kind()} str(" ") {v.text()})436			}437			Expr::ExprVar(n) => p!(new: {n.name()}),438			Expr::ExprLocal(l) => {439				let mut pi = p!(new:);440				let (binds, end_comments) = children_between::<Bind>(441					l.syntax().clone(),442					l.local_kw_token().map(Into::into).as_ref(),443					l.semi_token().map(Into::into).as_ref(),444				);445				if binds.len() == 1 {446					let bind = &binds[0];447					p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));448					p!(pi: str("local ") {bind.value});449				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?450				} else {451					p!(pi: str("local") >i nl);452					for bind in binds {453						if bind.needs_newline_above() {454							p!(pi: nl);455						}456						p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));457						p!(pi: {bind.value} str(";"));458						p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);459					}460					// TODO: needs_newline_above end_comments461					p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));462					p!(pi: <i);463				}464				p!(pi: str(";") nl);465466				let expr_comments = trivia_between(467					l.syntax().clone(),468					l.semi_token().map(Into::into).as_ref(),469					l.expr()470						.map(|e| e.syntax().clone())471						.map(Into::into)472						.as_ref(),473				);474				p!(pi: items(format_comments(&expr_comments, CommentLocation::AboveItem)));475476				// TODO: needs_newline_above expr477				p!(pi: {l.expr()});478				pi479			}480			Expr::ExprIfThenElse(ite) => {481				let mut pi =482					p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});483				if ite.else_kw_token().is_some() || ite.else_().is_some() {484					p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})485				}486				pi487			}488			Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),489			Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),490			Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),491			Expr::ExprLiteral(l) => {492				p!(new: {l.literal()})493			}494		}495	}496}497498impl Printable for SourceFile {499	fn print(&self) -> PrintItems {500		let mut pi = p!(new:);501		let before = trivia_before(502			self.syntax().clone(),503			self.expr()504				.map(|e| e.syntax().clone())505				.map(Into::into)506				.as_ref(),507		);508		let after = trivia_after(509			self.syntax().clone(),510			self.expr()511				.map(|e| e.syntax().clone())512				.map(Into::into)513				.as_ref(),514		);515		p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));516		p!(pi: {self.expr()} nl);517		p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));518		pi519	}520}521522fn main() {523	let (parsed, _errors) = jrsonnet_rowan_parser::parse(524		r#"525526527		# Edit me!528		local b = import "b.libsonnet";  # comment529		local a = import "a.libsonnet";530531			 local f(x,y)=x+y;532533		local {a: [b, ..., c], d, ...e} = null;534535		local ass = assert false : false; false;536537		local fn = function(a, b, c = 3) 4;538539		local comp = [a for b in c if d == e];540		local ocomp = {[k]: 1 for k in v};541542		local ? = skip;543544		local intr = $intrinsic(test);545		local intrId = $intrinsicId;546		local intrThisFile = $intrinsicThisFile;547548		local ie = a[expr];549550		local unary = !a;551552		local553			//   I am comment554			singleLocalWithItemComment = 1,555		;556557		// Comment between local and expression558559		local560			a = 1, //   Inline561			// Comment above b562			b = 4,563564			// c needs some space565			c = 5,566567			// Comment after everything568		;569570571		local Template = {z: "foo"};572573		{574						local575576					h = 3,577					assert self.a == 1578579					: "error",580		"f": ((((((3)))))) ,581		"g g":582		f(4,2),583		arr: [[584		  1, 2,585		  ],586		  3,587		  {588			  b: {589				  c: {590					  k: [16]591				  }592			  }593		  }594		  ],595		  m: a[1::],596		  m: b[::],597598		  comments: {599			_: '',600			//     Plain comment601			a: '',602603			#    Plain comment with empty line before604			b: '',605			/*Single-line multiline comment606607			*/608			c: '',609610			/**Single-line multiline doc comment611612			*/613			c: '',614615			/**multiline doc comment616			s617			*/618			c: '',619620			/*621622	Multi-line623624	comment625			*/626			d: '',627628			e: '', // Inline comment629630			k: '',631632			// Text after everything633		  },634		  comments2: {635			k: '',636			// Text after everything, but no newline above637		  },638		  k: if a         == b    then639640641		  2642643		  else Template {}644		} + Template645646647		// Comment after everything648"#,649	);650651	// dbg!(errors);652	dbg!(&parsed);653654	let o = dprint_core::formatting::format(655		|| parsed.print(),656		PrintOptions {657			indent_width: 2,658			max_width: 100,659			use_tabs: false,660			new_line_text: "\n",661		},662	);663	println!("{}", o);664}