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
before · 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},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: str("local") >i nl);440				for bind in l.binds() {441					p!(pi: {bind} str(",") nl);442				}443				p!(pi: <i str(";") nl {l.expr()});444				pi445			}446			Expr::ExprIfThenElse(ite) => {447				let mut pi =448					p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});449				if ite.else_kw_token().is_some() || ite.else_().is_some() {450					p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})451				}452				pi453			}454			Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),455			Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),456			Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),457			Expr::ExprLiteral(l) => {458				p!(new: {l.literal()})459			}460		}461	}462}463464impl Printable for SourceFile {465	fn print(&self) -> PrintItems {466		let mut pi = p!(new:);467		let before = trivia_before(468			self.syntax().clone(),469			self.expr()470				.map(|e| e.syntax().clone())471				.map(Into::into)472				.as_ref(),473		);474		let after = trivia_after(475			self.syntax().clone(),476			self.expr()477				.map(|e| e.syntax().clone())478				.map(Into::into)479				.as_ref(),480		);481		p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));482		p!(pi: {self.expr()} nl);483		p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));484		pi485	}486}487488fn main() {489	let (parsed, _errors) = jrsonnet_rowan_parser::parse(490		r#"491492493		# Edit me!494		local b = import "b.libsonnet";  # comment495		local a = import "a.libsonnet";496497			 local f(x,y)=x+y;498499		local {a: [b, ..., c], d, ...e} = null;500501		local ass = assert false : false; false;502503		local fn = function(a, b, c = 3) 4;504505		local comp = [a for b in c if d == e];506		local ocomp = {[k]: 1 for k in v};507508		local ? = skip;509510		local intr = $intrinsic(test);511		local intrId = $intrinsicId;512		local intrThisFile = $intrinsicThisFile;513514		local ie = a[expr];515516		local unary = !a;517518		local Template = {z: "foo"};519520		{521						local522523					h = 3,524					assert self.a == 1525526					: "error",527		"f": ((((((3)))))) ,528		"g g":529		f(4,2),530		arr: [[531		  1, 2,532		  ],533		  3,534		  {535			  b: {536				  c: {537					  k: [16]538				  }539			  }540		  }541		  ],542		  m: a[1::],543		  m: b[::],544545		  comments: {546			_: '',547			//     Plain comment548			a: '',549550			#    Plain comment with empty line before551			b: '',552			/*Single-line multiline comment553554			*/555			c: '',556557			/**Single-line multiline doc comment558559			*/560			c: '',561562			/**multiline doc comment563			s564			*/565			c: '',566567			/*568569	Multi-line570571	comment572			*/573			d: '',574575			e: '', // Inline comment576577			k: '',578579			// Text after everything580		  },581		  comments2: {582			k: '',583			// Text after everything, but no newline above584		  },585		  k: if a         == b    then586587588		  2589590		  else Template {}591		} + Template592593594		// Comment after everything595"#,596	);597598	// dbg!(errors);599	dbg!(&parsed);600601	let o = dprint_core::formatting::format(602		|| parsed.print(),603		PrintOptions {604			indent_width: 2,605			max_width: 100,606			use_tabs: false,607			new_line_text: "\n",608		},609	);610	println!("{}", o);611}