git.delta.rocks / jrsonnet / refs/commits / 0c7230b6eb21

difftreelog

fix(fmt) multi-line ArgsDesc

nxklpotmYaroslav Bolyukin2026-02-08parent: #36e84a6.patch.diff
in: master

8 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -644,6 +644,8 @@
 dependencies = [
  "dprint-core",
  "hi-doc",
+ "indoc",
+ "insta",
  "jrsonnet-rowan-parser",
 ]
 
modifiedcmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -1,4 +1,9 @@
-use std::{fs, io};
+use std::{
+	fs,
+	io::{self, Write as _},
+	path::PathBuf,
+	process,
+};
 
 use clap::Parser;
 use jrsonnet_formatter::{format, FormatOptions};
modifiedcrates/jrsonnet-formatter/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-formatter/Cargo.toml
+++ b/crates/jrsonnet-formatter/Cargo.toml
@@ -9,6 +9,8 @@
 [dependencies]
 dprint-core.workspace = true
 hi-doc.workspace = true
+indoc.workspace = true
+insta.workspace = true
 jrsonnet-rowan-parser.workspace = true
 
 [lints]
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-formatter/src/lib.rs
1use std::{any::type_name, rc::Rc};23use children::{children_between, trivia_before};4use dprint_core::formatting::{5	condition_helpers::is_multiple_lines, condition_resolvers::true_resolver,6	ConditionResolverContext, LineNumber, PrintItems, PrintOptions,7};8use hi_doc::{Formatting, SnippetBuilder};9use jrsonnet_rowan_parser::{10	nodes::{11		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,12		DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,13		Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,14		UnaryOperator, Visibility,15	},16	AstNode, AstToken as _, SyntaxToken,17};1819use crate::{20	children::trivia_after,21	comments::{format_comments, CommentLocation},22};2324mod children;25mod comments;26#[cfg(test)]27mod tests;2829pub trait Printable {30	fn print(&self, out: &mut PrintItems);31}3233macro_rules! pi {34	(@i; $($t:tt)*) => {{35		#[allow(unused_mut)]36		let mut o = dprint_core::formatting::PrintItems::new();37		pi!(@s; o: $($t)*);38		o39	}};40	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{41		$o.push_string($e.to_owned());42		pi!(@s; $o: $($t)*);43	}};44	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{45		$o.push_string($e);46		pi!(@s; $o: $($t)*);47	}};48	(@s; $o:ident: nl $($t:tt)*) => {{49		$o.push_signal(dprint_core::formatting::Signal::NewLine);50		pi!(@s; $o: $($t)*);51	}};52	(@s; $o:ident: tab $($t:tt)*) => {{53		$o.push_signal(dprint_core::formatting::Signal::Tab);54		pi!(@s; $o: $($t)*);55	}};56	(@s; $o:ident: >i $($t:tt)*) => {{57		$o.push_signal(dprint_core::formatting::Signal::StartIndent);58		pi!(@s; $o: $($t)*);59	}};60	(@s; $o:ident: <i $($t:tt)*) => {{61		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);62		pi!(@s; $o: $($t)*);63	}};64	(@s; $o:ident: info($v:expr) $($t:tt)*) => {{65		$o.push_info($v);66		pi!(@s; $o: $($t)*);67	}};68	(@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{69		$o.push_condition(dprint_core::formatting::conditions::if_true(70			$s,71			$cond.clone(),72			{73				let mut o = PrintItems::new();74				p!(o, $($i)*);75				o76			},77		));78		pi!(@s; $o: $($t)*);79	}};80	(@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{81		$o.push_condition(dprint_core::formatting::conditions::if_true_or(82			$s,83			$cond.clone(),84			{85				let mut o = PrintItems::new();86				p!(o, $($i)*);87				o88			},89			{90				let mut o = PrintItems::new();91				p!(o, $($e)*);92				o93			},94		));95		pi!(@s; $o: $($t)*);96	}};97	(@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{98		$o.push_condition(dprint_core::formatting::conditions::if_true_or(99			$s,100			$cond.clone(),101			{102				let o = PrintItems::new();103				o104			},105			{106				let mut o = PrintItems::new();107				p!(o, $($e)*);108				o109			},110		));111		pi!(@s; $o: $($t)*);112	}};113	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{114		$expr.print($o);115		pi!(@s; $o: $($t)*);116	}};117	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{118		$o.extend($expr);119		pi!(@s; $o: $($t)*);120	}};121	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{122		if $e {123			pi!(@s; $o: $($then)*);124		}125		pi!(@s; $o: $($t)*);126	}};127	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{128		if $e {129			pi!(@s; $o: $($then)*);130		} else {131			pi!(@s; $o: $($else)*);132		}133		pi!(@s; $o: $($t)*);134	}};135	(@s; $i:ident:) => {}136}137macro_rules! p {138	($o:ident, $($t:tt)*) => {139		pi!(@s; $o: $($t)*)140	};141}142pub(crate) use p;143pub(crate) use pi;144145impl<P> Printable for Option<P>146where147	P: Printable,148{149	fn print(&self, out: &mut PrintItems) {150		if let Some(v) = self {151			v.print(out);152		} else {153			p!(154				out,155				string(format!(156					"/*missing {}*/",157					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")158				),)159			);160		}161	}162}163164impl Printable for SyntaxToken {165	fn print(&self, out: &mut PrintItems) {166		p!(out, string(self.to_string()));167	}168}169170impl Printable for Text {171	fn print(&self, out: &mut PrintItems) {172		p!(out, string(format!("{}", self)));173	}174}175impl Printable for Number {176	fn print(&self, out: &mut PrintItems) {177		p!(out, string(format!("{}", self)));178	}179}180181impl Printable for Name {182	fn print(&self, out: &mut PrintItems) {183		p!(out, { self.ident_lit() });184	}185}186187impl Printable for DestructRest {188	fn print(&self, out: &mut PrintItems) {189		p!(out, str("..."));190		if let Some(name) = self.into() {191			p!(out, { name });192		}193	}194}195196impl Printable for Destruct {197	fn print(&self, out: &mut PrintItems) {198		match self {199			Self::DestructFull(f) => {200				p!(out, { f.name() });201			}202			Self::DestructSkip(_) => p!(out, str("?")),203			Self::DestructArray(a) => {204				p!(out, str("[") >i nl);205				for el in a.destruct_array_parts() {206					match el {207						DestructArrayPart::DestructArrayElement(e) => {208							p!(out, {e.destruct()} str(",") nl);209						}210						DestructArrayPart::DestructRest(d) => {211							p!(out, {d} str(",") nl);212						}213					}214				}215				p!(out, <i str("]"));216			}217			Self::DestructObject(o) => {218				p!(out, str("{") >i nl);219				for item in o.destruct_object_fields() {220					p!(out, { item.field() });221					if let Some(des) = item.destruct() {222						p!(out, str(": ") {des});223					}224					if let Some(def) = item.expr() {225						p!(out, str(" = ") {def});226					}227					p!(out, str(",") nl);228				}229				if let Some(rest) = o.destruct_rest() {230					p!(out, {rest} nl);231				}232				p!(out, <i str("}"));233			}234		}235	}236}237238impl Printable for FieldName {239	fn print(&self, out: &mut PrintItems) {240		match self {241			Self::FieldNameFixed(f) => {242				if let Some(id) = f.id() {243					p!(out, { id });244				} else if let Some(str) = f.text() {245					p!(out, { str });246				} else {247					p!(out, str("/*missing FieldName*/"));248				}249			}250			Self::FieldNameDynamic(d) => {251				p!(out, str("[") {d.expr()} str("]"));252			}253		}254	}255}256257impl Printable for Visibility {258	fn print(&self, out: &mut PrintItems) {259		p!(out, string(self.to_string()));260	}261}262263impl Printable for ObjLocal {264	fn print(&self, out: &mut PrintItems) {265		p!(out, str("local ") {self.bind()});266	}267}268269impl Printable for Assertion {270	fn print(&self, out: &mut PrintItems) {271		p!(out, str("assert ") {self.condition()});272		if self.colon_token().is_some() || self.message().is_some() {273			p!(out, str(": ") {self.message()});274		}275	}276}277278impl Printable for ParamsDesc {279	fn print(&self, out: &mut PrintItems) {280		p!(out, str("(") >i nl);281		for param in self.params() {282			p!(out, { param.destruct() });283			if param.assign_token().is_some() || param.expr().is_some() {284				p!(out, str(" = ") {param.expr()});285			}286			p!(out, str(",") nl);287		}288		p!(out, <i str(")"));289	}290}291impl Printable for ArgsDesc {292	fn print(&self, out: &mut PrintItems) {293		let start = LineNumber::new("start");294		let end = LineNumber::new("end");295		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {296			is_multiple_lines(condition_context, start, end).map(|v| !v)297		});298		p!(out, str("(") info(start) if("start args", multi_line, >i nl));299		let (children, end_comments) = children_between::<Arg>(300			self.syntax().clone(),301			self.l_paren_token().map(Into::into).as_ref(),302			self.r_paren_token().map(Into::into).as_ref(),303			None,304		);305		let mut args = children.into_iter().peekable();306		while let Some(ele) = args.next() {307			if ele.should_start_with_newline {308				p!(out, nl);309			}310			format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);311			let arg = ele.value;312			if arg.name().is_some() || arg.assign_token().is_some() {313				p!(out, {arg.name()} str(" = "));314			}315			let comma_between = if args.peek().is_some() {316				true_resolver()317			} else {318				multi_line.clone()319			};320			p!(out, {arg.expr()} if("arg comma", comma_between, str(",") if_not("between args", multi_line, str(" "))));321			format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);322			p!(out, if("between args", multi_line, nl));323		}324		if end_comments.should_start_with_newline {325			p!(out, nl);326		}327		format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);328		p!(out, if("end args", multi_line, <i info(end)) str(")"));329	}330}331impl Printable for SliceDesc {332	fn print(&self, out: &mut PrintItems) {333		p!(out, str("["));334		if self.from().is_some() {335			p!(out, { self.from() });336		}337		p!(out, str(":"));338		if self.end().is_some() {339			p!(out, { self.end().map(|e| e.expr()) });340		}341		// Keep only one : in case if we don't need step342		if self.step().is_some() {343			p!(out, str(":") {self.step().map(|e|e.expr())});344		}345		p!(out, str("]"));346	}347}348349impl Printable for Member {350	fn print(&self, out: &mut PrintItems) {351		match self {352			Self::MemberBindStmt(b) => {353				p!(out, { b.obj_local() });354			}355			Self::MemberAssertStmt(ass) => {356				p!(out, { ass.assertion() });357			}358			Self::MemberFieldNormal(n) => {359				p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});360			}361			Self::MemberFieldMethod(m) => {362				p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});363			}364		}365	}366}367368impl Printable for ObjBody {369	fn print(&self, out: &mut PrintItems) {370		match self {371			Self::ObjBodyComp(l) => {372				let (children, mut end_comments) = children_between::<Member>(373					l.syntax().clone(),374					l.l_brace_token().map(Into::into).as_ref(),375					Some(376						&(l.comp_specs()377							.next()378							.expect("at least one spec is defined")379							.syntax()380							.clone())381						.into(),382					),383					None,384				);385				let trailing_for_comp = end_comments.extract_trailing();386				p!(out, str("{") >i nl);387				for mem in children {388					if mem.should_start_with_newline {389						p!(out, nl);390					}391					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);392					p!(out, {mem.value} str(","));393					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);394					p!(out, nl);395				}396397				if end_comments.should_start_with_newline {398					p!(out, nl);399				}400				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);401402				let (compspecs, end_comments) = children_between::<CompSpec>(403					l.syntax().clone(),404					l.member_comps()405						.last()406						.map(|m| m.syntax().clone())407						.map(Into::into)408						.or_else(|| l.l_brace_token().map(Into::into))409						.as_ref(),410					l.r_brace_token().map(Into::into).as_ref(),411					Some(trailing_for_comp),412				);413				for mem in compspecs {414					if mem.should_start_with_newline {415						p!(out, nl);416					}417					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);418					p!(out, { mem.value });419					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);420				}421				if end_comments.should_start_with_newline {422					p!(out, nl);423				}424				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);425426				p!(out, nl <i str("}"));427			}428			Self::ObjBodyMemberList(l) => {429				let (children, end_comments) = children_between::<Member>(430					l.syntax().clone(),431					l.l_brace_token().map(Into::into).as_ref(),432					l.r_brace_token().map(Into::into).as_ref(),433					None,434				);435				if children.is_empty() && end_comments.is_empty() {436					p!(out, str("{ }"));437					return;438				}439				p!(out, str("{") >i nl);440				for (i, mem) in children.into_iter().enumerate() {441					if mem.should_start_with_newline && i != 0 {442						p!(out, nl);443					}444					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);445					p!(out, {mem.value} str(","));446					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);447					p!(out, nl);448				}449450				if end_comments.should_start_with_newline {451					p!(out, nl);452				}453				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);454				p!(out, <i str("}"));455			}456		}457	}458}459impl Printable for UnaryOperator {460	fn print(&self, out: &mut PrintItems) {461		p!(out, string(self.text().to_string()));462	}463}464impl Printable for BinaryOperator {465	fn print(&self, out: &mut PrintItems) {466		p!(out, string(self.text().to_string()));467	}468}469impl Printable for Bind {470	fn print(&self, out: &mut PrintItems) {471		match self {472			Self::BindDestruct(d) => {473				p!(out, {d.into()} str(" = ") {d.value()});474			}475			Self::BindFunction(f) => {476				p!(out, {f.name()} {f.params()} str(" = ") {f.value()});477			}478		}479	}480}481impl Printable for Literal {482	fn print(&self, out: &mut PrintItems) {483		p!(out, string(self.syntax().to_string()));484	}485}486impl Printable for ImportKind {487	fn print(&self, out: &mut PrintItems) {488		p!(out, string(self.syntax().to_string()));489	}490}491impl Printable for ForSpec {492	fn print(&self, out: &mut PrintItems) {493		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});494	}495}496impl Printable for IfSpec {497	fn print(&self, out: &mut PrintItems) {498		p!(out, str("if ") {self.expr()});499	}500}501impl Printable for CompSpec {502	fn print(&self, out: &mut PrintItems) {503		match self {504			Self::ForSpec(f) => f.print(out),505			Self::IfSpec(i) => i.print(out),506		}507	}508}509impl Printable for Expr {510	fn print(&self, out: &mut PrintItems) {511		let (stmts, _ending) = children_between::<Stmt>(512			self.syntax().clone(),513			None,514			self.expr_base()515				.as_ref()516				.map(ExprBase::syntax)517				.cloned()518				.map(Into::into)519				.as_ref(),520			None,521		);522		for stmt in stmts {523			p!(out, { stmt.value });524		}525		p!(out, { self.expr_base() });526		let (suffixes, _ending) = children_between::<Suffix>(527			self.syntax().clone(),528			self.expr_base()529				.as_ref()530				.map(ExprBase::syntax)531				.cloned()532				.map(Into::into)533				.as_ref(),534			None,535			None,536		);537		for suffix in suffixes {538			p!(out, { suffix.value });539		}540	}541}542impl Printable for Suffix {543	fn print(&self, out: &mut PrintItems) {544		match self {545			Self::SuffixIndex(i) => {546				if i.question_mark_token().is_some() {547					p!(out, str("?"));548				}549				p!(out, str(".") {i.index()});550			}551			Self::SuffixIndexExpr(e) => {552				if e.question_mark_token().is_some() {553					p!(out, str(".?"));554				}555				p!(out, str("[") {e.index()} str("]"));556			}557			Self::SuffixSlice(d) => {558				p!(out, { d.slice_desc() });559			}560			Self::SuffixApply(a) => {561				p!(out, { a.args_desc() });562			}563		}564	}565}566impl Printable for Stmt {567	fn print(&self, out: &mut PrintItems) {568		match self {569			Self::StmtLocal(l) => {570				let (binds, end_comments) = children_between::<Bind>(571					l.syntax().clone(),572					l.local_kw_token().map(Into::into).as_ref(),573					l.semi_token().map(Into::into).as_ref(),574					None,575				);576				if binds.len() == 1 {577					let bind = &binds[0];578					format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);579					p!(out, str("local ") {bind.value});580				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?581				} else {582					p!(out,str("local") >i nl);583					for bind in binds {584						if bind.should_start_with_newline {585							p!(out, nl);586						}587						format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);588						p!(out, {bind.value} str(","));589						format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);590						p!(out, nl);591					}592					if end_comments.should_start_with_newline {593						p!(out, nl);594					}595					format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);596					p!(out,<i);597				}598				p!(out,str(";") nl);599			}600			Self::StmtAssert(a) => {601				p!(out, {a.assertion()} str(";") nl);602			}603		}604	}605}606impl Printable for ExprBase {607	fn print(&self, out: &mut PrintItems) {608		match self {609			Self::ExprBinary(b) => {610				p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});611			}612			Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),613			// Self::ExprSlice(s) => {614			// 	p!(new: {s.expr()} {s.slice_desc()})615			// }616			// Self::ExprIndex(i) => {617			// 	p!(new: {i.expr()} str(".") {i.index()})618			// }619			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),620			// Self::ExprApply(a) => {621			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});622			// 	if a.tailstrict_kw_token().is_some() {623			// 		p!(out,str(" tailstrict"));624			// 	}625			// 	pi626			// }627			Self::ExprObjExtend(ex) => {628				p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});629			}630			Self::ExprParened(p) => {631				p!(out, str("(") {p.expr()} str(")"));632			}633			Self::ExprString(s) => p!(out, { s.text() }),634			Self::ExprNumber(n) => p!(out, { n.number() }),635			Self::ExprArray(a) => {636				p!(out, str("[") >i nl);637				for el in a.exprs() {638					p!(out, {el} str(",") nl);639				}640				p!(out, <i str("]"));641			}642			Self::ExprObject(obj) => {643				p!(out, { obj.obj_body() });644			}645			Self::ExprArrayComp(arr) => {646				p!(out, str("[") {arr.expr()});647				for spec in arr.comp_specs() {648					p!(out, str(" ") {spec});649				}650				p!(out, str("]"));651			}652			Self::ExprImport(v) => {653				p!(out, {v.import_kind()} str(" ") {v.text()});654			}655			Self::ExprVar(n) => p!(out, { n.name() }),656			// Self::ExprLocal(l) => {657			// }658			Self::ExprIfThenElse(ite) => {659				p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});660				if ite.else_kw_token().is_some() || ite.else_().is_some() {661					p!(out, str(" else ") {ite.else_().map(|t| t.expr())});662				}663			}664			Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),665			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),666			Self::ExprError(e) => p!(out, str("error ") {e.expr()}),667			Self::ExprLiteral(l) => {668				p!(out, { l.literal() });669			}670		}671	}672}673674impl Printable for SourceFile {675	fn print(&self, out: &mut PrintItems) {676		let before = trivia_before(677			self.syntax().clone(),678			self.expr()679				.map(|e| e.syntax().clone())680				.map(Into::into)681				.as_ref(),682		);683		let after = trivia_after(684			self.syntax().clone(),685			self.expr()686				.map(|e| e.syntax().clone())687				.map(Into::into)688				.as_ref(),689		);690		format_comments(&before, CommentLocation::AboveItem, out);691		p!(out, {self.expr()} nl);692		format_comments(&after, CommentLocation::EndOfItems, out);693	}694}695696pub struct FormatOptions {697	// 0 for hard tabs698	pub indent: u8,699}700pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {701	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);702	if !errors.is_empty() {703		let mut builder = hi_doc::SnippetBuilder::new(input);704		for error in errors {705			builder706				.error(hi_doc::Text::fragment(707					format!("{:?}", error.error),708					Formatting::default(),709				))710				.range(711					error.range.start().into()712						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),713				)714				.build();715		}716		// let snippet = builder.build();717		return Err(builder);718		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip719		// ERROR rowan nodes.720		// Recovery needs to be enabled for LSP, though.721	}722	Ok(dprint_core::formatting::format(723		|| {724			let mut out = PrintItems::new();725			parsed.print(&mut out);726			out727		},728		PrintOptions {729			indent_width: if opts.indent == 0 {730				// Reasonable max length for both 2 and 4 space sized tabs.731				3732			} else {733				opts.indent734			},735			max_width: 100,736			use_tabs: opts.indent == 0,737			new_line_text: "\n",738		},739	))740}
after · crates/jrsonnet-formatter/src/lib.rs
1use std::{any::type_name, rc::Rc};23use children::{children_between, trivia_before};4use dprint_core::formatting::{5	condition_helpers::is_multiple_lines,6	ir_helpers::{new_line_group, with_indent},7	ConditionResolver, ConditionResolverContext, LineNumber, PrintItems, PrintOptions,8};9use hi_doc::{Formatting, SnippetBuilder};10use jrsonnet_rowan_parser::{11	nodes::{12		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,13		DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,14		Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,15		UnaryOperator, Visibility,16	},17	AstNode, AstToken as _, SyntaxToken,18};1920use crate::{21	children::{trivia_after, Child},22	comments::{format_comments, CommentLocation},23};2425mod children;26mod comments;27#[cfg(test)]28mod tests;2930pub trait Printable {31	fn print(&self, out: &mut PrintItems);32}3334macro_rules! pi {35	(@i; $($t:tt)*) => {{36		#[allow(unused_mut)]37		let mut o = dprint_core::formatting::PrintItems::new();38		pi!(@s; o: $($t)*);39		o40	}};41	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{42		$o.push_string($e.to_owned());43		pi!(@s; $o: $($t)*);44	}};45	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{46		$o.push_string($e);47		pi!(@s; $o: $($t)*);48	}};49	(@s; $o:ident: nl $($t:tt)*) => {{50		$o.push_signal(dprint_core::formatting::Signal::NewLine);51		pi!(@s; $o: $($t)*);52	}};53	(@s; $o:ident: sonl $($t:tt)*) => {{54		$o.push_signal(dprint_core::formatting::Signal::SpaceOrNewLine);55		pi!(@s; $o: $($t)*);56	}};57	(@s; $o:ident: tab $($t:tt)*) => {{58		$o.push_signal(dprint_core::formatting::Signal::Tab);59		pi!(@s; $o: $($t)*);60	}};61	(@s; $o:ident: >i $($t:tt)*) => {{62		$o.push_signal(dprint_core::formatting::Signal::StartIndent);63		pi!(@s; $o: $($t)*);64	}};65	(@s; $o:ident: <i $($t:tt)*) => {{66		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);67		pi!(@s; $o: $($t)*);68	}};69	(@s; $o:ident: info($v:expr) $($t:tt)*) => {{70		$o.push_info($v);71		pi!(@s; $o: $($t)*);72	}};73	(@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{74		$o.push_anchor(LineNumberAnchor::new($v));75		pi!(@s; $o: $($t)*);76	}};77	(@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{78		$o.push_condition(dprint_core::formatting::conditions::if_true(79			$s,80			$cond.clone(),81			{82				let mut o = PrintItems::new();83				p!(o, $($i)*);84				o85			},86		));87		pi!(@s; $o: $($t)*);88	}};89	(@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{90		$o.push_condition(dprint_core::formatting::conditions::if_true_or(91			$s,92			$cond.clone(),93			{94				let mut o = PrintItems::new();95				p!(o, $($i)*);96				o97			},98			{99				let mut o = PrintItems::new();100				p!(o, $($e)*);101				o102			},103		));104		pi!(@s; $o: $($t)*);105	}};106	(@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{107		$o.push_condition(dprint_core::formatting::conditions::if_true_or(108			$s,109			$cond.clone(),110			{111				let o = PrintItems::new();112				o113			},114			{115				let mut o = PrintItems::new();116				p!(o, $($e)*);117				o118			},119		));120		pi!(@s; $o: $($t)*);121	}};122	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{123		$expr.print($o);124		pi!(@s; $o: $($t)*);125	}};126	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{127		$o.extend($expr);128		pi!(@s; $o: $($t)*);129	}};130	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{131		if $e {132			pi!(@s; $o: $($then)*);133		}134		pi!(@s; $o: $($t)*);135	}};136	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{137		if $e {138			pi!(@s; $o: $($then)*);139		} else {140			pi!(@s; $o: $($else)*);141		}142		pi!(@s; $o: $($t)*);143	}};144	(@s; $i:ident:) => {}145}146macro_rules! p {147	($o:ident, $($t:tt)*) => {148		pi!(@s; $o: $($t)*)149	};150}151pub(crate) use p;152pub(crate) use pi;153154impl<P> Printable for Option<P>155where156	P: Printable,157{158	fn print(&self, out: &mut PrintItems) {159		if let Some(v) = self {160			v.print(out);161		} else {162			p!(163				out,164				string(format!(165					"/*missing {}*/",166					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")167				),)168			);169		}170	}171}172173impl Printable for SyntaxToken {174	fn print(&self, out: &mut PrintItems) {175		p!(out, string(self.to_string()));176	}177}178179impl Printable for Text {180	fn print(&self, out: &mut PrintItems) {181		p!(out, string(format!("{}", self)));182	}183}184impl Printable for Number {185	fn print(&self, out: &mut PrintItems) {186		p!(out, string(format!("{}", self)));187	}188}189190impl Printable for Name {191	fn print(&self, out: &mut PrintItems) {192		p!(out, { self.ident_lit() });193	}194}195196impl Printable for DestructRest {197	fn print(&self, out: &mut PrintItems) {198		p!(out, str("..."));199		if let Some(name) = self.into() {200			p!(out, { name });201		}202	}203}204205impl Printable for Destruct {206	fn print(&self, out: &mut PrintItems) {207		match self {208			Self::DestructFull(f) => {209				p!(out, { f.name() });210			}211			Self::DestructSkip(_) => p!(out, str("?")),212			Self::DestructArray(a) => {213				p!(out, str("[") >i nl);214				for el in a.destruct_array_parts() {215					match el {216						DestructArrayPart::DestructArrayElement(e) => {217							p!(out, {e.destruct()} str(",") nl);218						}219						DestructArrayPart::DestructRest(d) => {220							p!(out, {d} str(",") nl);221						}222					}223				}224				p!(out, <i str("]"));225			}226			Self::DestructObject(o) => {227				p!(out, str("{") >i nl);228				for item in o.destruct_object_fields() {229					p!(out, { item.field() });230					if let Some(des) = item.destruct() {231						p!(out, str(": ") {des});232					}233					if let Some(def) = item.expr() {234						p!(out, str(" = ") {def});235					}236					p!(out, str(",") nl);237				}238				if let Some(rest) = o.destruct_rest() {239					p!(out, {rest} nl);240				}241				p!(out, <i str("}"));242			}243		}244	}245}246247impl Printable for FieldName {248	fn print(&self, out: &mut PrintItems) {249		match self {250			Self::FieldNameFixed(f) => {251				if let Some(id) = f.id() {252					p!(out, { id });253				} else if let Some(str) = f.text() {254					p!(out, { str });255				} else {256					p!(out, str("/*missing FieldName*/"));257				}258			}259			Self::FieldNameDynamic(d) => {260				p!(out, str("[") {d.expr()} str("]"));261			}262		}263	}264}265266impl Printable for Visibility {267	fn print(&self, out: &mut PrintItems) {268		p!(out, string(self.to_string()));269	}270}271272impl Printable for ObjLocal {273	fn print(&self, out: &mut PrintItems) {274		p!(out, str("local ") {self.bind()});275	}276}277278impl Printable for Assertion {279	fn print(&self, out: &mut PrintItems) {280		p!(out, str("assert ") {self.condition()});281		if self.colon_token().is_some() || self.message().is_some() {282			p!(out, str(": ") {self.message()});283		}284	}285}286287impl Printable for ParamsDesc {288	fn print(&self, out: &mut PrintItems) {289		p!(out, str("(") >i nl);290		for param in self.params() {291			p!(out, { param.destruct() });292			if param.assign_token().is_some() || param.expr().is_some() {293				p!(out, str(" = ") {param.expr()});294			}295			p!(out, str(",") nl);296		}297		p!(out, <i str(")"));298	}299}300impl Printable for ArgsDesc {301	fn print(&self, out: &mut PrintItems) {302		let start = LineNumber::new("args start line");303		let end = LineNumber::new("args end line");304		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {305			is_multiple_lines(condition_context, start, end)306		});307308		let (children, end_comments) = children_between::<Arg>(309			self.syntax().clone(),310			self.l_paren_token().map(Into::into).as_ref(),311			self.r_paren_token().map(Into::into).as_ref(),312			None,313		);314315		fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {316			let mut _out = PrintItems::new();317			let out = &mut _out;318319			let mut args = children.into_iter().peekable();320			while let Some(ele) = args.next() {321				if ele.should_start_with_newline {322					p!(out, nl);323				}324				format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);325				let arg = ele.value;326				if arg.name().is_some() || arg.assign_token().is_some() {327					p!(out, {arg.name()} str(" = "));328				}329				p!(out, { arg.expr() });330				let has_more = args.peek().is_some();331				if has_more {332					p!(out, str(","));333				} else {334					p!(out, if("trailing comma", multi_line, str(",")));335				}336				format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);337				if has_more {338					p!(out, if_else("arg separator", multi_line, nl)(sonl));339				}340			}341			_out342		}343344		let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();345		let args_indented = with_indent(pi!(@i; nl items(args_items.into())));346347		p!(out, str("(") info(start));348		p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));349		if end_comments.should_start_with_newline {350			p!(out, nl);351		}352		format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);353		p!(out, str(")") info(end));354	}355}356impl Printable for SliceDesc {357	fn print(&self, out: &mut PrintItems) {358		p!(out, str("["));359		if self.from().is_some() {360			p!(out, { self.from() });361		}362		p!(out, str(":"));363		if self.end().is_some() {364			p!(out, { self.end().map(|e| e.expr()) });365		}366		// Keep only one : in case if we don't need step367		if self.step().is_some() {368			p!(out, str(":") {self.step().map(|e|e.expr())});369		}370		p!(out, str("]"));371	}372}373374impl Printable for Member {375	fn print(&self, out: &mut PrintItems) {376		match self {377			Self::MemberBindStmt(b) => {378				p!(out, { b.obj_local() });379			}380			Self::MemberAssertStmt(ass) => {381				p!(out, { ass.assertion() });382			}383			Self::MemberFieldNormal(n) => {384				p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});385			}386			Self::MemberFieldMethod(m) => {387				p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});388			}389		}390	}391}392393impl Printable for ObjBody {394	fn print(&self, out: &mut PrintItems) {395		match self {396			Self::ObjBodyComp(l) => {397				let (children, mut end_comments) = children_between::<Member>(398					l.syntax().clone(),399					l.l_brace_token().map(Into::into).as_ref(),400					Some(401						&(l.comp_specs()402							.next()403							.expect("at least one spec is defined")404							.syntax()405							.clone())406						.into(),407					),408					None,409				);410				let trailing_for_comp = end_comments.extract_trailing();411				p!(out, str("{") >i nl);412				for mem in children {413					if mem.should_start_with_newline {414						p!(out, nl);415					}416					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);417					p!(out, {mem.value} str(","));418					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);419					p!(out, nl);420				}421422				if end_comments.should_start_with_newline {423					p!(out, nl);424				}425				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);426427				let (compspecs, end_comments) = children_between::<CompSpec>(428					l.syntax().clone(),429					l.member_comps()430						.last()431						.map(|m| m.syntax().clone())432						.map(Into::into)433						.or_else(|| l.l_brace_token().map(Into::into))434						.as_ref(),435					l.r_brace_token().map(Into::into).as_ref(),436					Some(trailing_for_comp),437				);438				for mem in compspecs {439					if mem.should_start_with_newline {440						p!(out, nl);441					}442					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);443					p!(out, { mem.value });444					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);445				}446				if end_comments.should_start_with_newline {447					p!(out, nl);448				}449				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);450451				p!(out, nl <i str("}"));452			}453			Self::ObjBodyMemberList(l) => {454				let (children, end_comments) = children_between::<Member>(455					l.syntax().clone(),456					l.l_brace_token().map(Into::into).as_ref(),457					l.r_brace_token().map(Into::into).as_ref(),458					None,459				);460				if children.is_empty() && end_comments.is_empty() {461					p!(out, str("{ }"));462					return;463				}464				p!(out, str("{") >i nl);465				for (i, mem) in children.into_iter().enumerate() {466					if mem.should_start_with_newline && i != 0 {467						p!(out, nl);468					}469					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);470					p!(out, {mem.value} str(","));471					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);472					p!(out, nl);473				}474475				if end_comments.should_start_with_newline {476					p!(out, nl);477				}478				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);479				p!(out, <i str("}"));480			}481		}482	}483}484impl Printable for UnaryOperator {485	fn print(&self, out: &mut PrintItems) {486		p!(out, string(self.text().to_string()));487	}488}489impl Printable for BinaryOperator {490	fn print(&self, out: &mut PrintItems) {491		p!(out, string(self.text().to_string()));492	}493}494impl Printable for Bind {495	fn print(&self, out: &mut PrintItems) {496		match self {497			Self::BindDestruct(d) => {498				p!(out, {d.into()} str(" = ") {d.value()});499			}500			Self::BindFunction(f) => {501				p!(out, {f.name()} {f.params()} str(" = ") {f.value()});502			}503		}504	}505}506impl Printable for Literal {507	fn print(&self, out: &mut PrintItems) {508		p!(out, string(self.syntax().to_string()));509	}510}511impl Printable for ImportKind {512	fn print(&self, out: &mut PrintItems) {513		p!(out, string(self.syntax().to_string()));514	}515}516impl Printable for ForSpec {517	fn print(&self, out: &mut PrintItems) {518		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});519	}520}521impl Printable for IfSpec {522	fn print(&self, out: &mut PrintItems) {523		p!(out, str("if ") {self.expr()});524	}525}526impl Printable for CompSpec {527	fn print(&self, out: &mut PrintItems) {528		match self {529			Self::ForSpec(f) => f.print(out),530			Self::IfSpec(i) => i.print(out),531		}532	}533}534impl Printable for Expr {535	fn print(&self, out: &mut PrintItems) {536		let (stmts, _ending) = children_between::<Stmt>(537			self.syntax().clone(),538			None,539			self.expr_base()540				.as_ref()541				.map(ExprBase::syntax)542				.cloned()543				.map(Into::into)544				.as_ref(),545			None,546		);547		for stmt in stmts {548			p!(out, { stmt.value });549		}550		p!(out, { self.expr_base() });551		let (suffixes, _ending) = children_between::<Suffix>(552			self.syntax().clone(),553			self.expr_base()554				.as_ref()555				.map(ExprBase::syntax)556				.cloned()557				.map(Into::into)558				.as_ref(),559			None,560			None,561		);562		for suffix in suffixes {563			p!(out, { suffix.value });564		}565	}566}567impl Printable for Suffix {568	fn print(&self, out: &mut PrintItems) {569		match self {570			Self::SuffixIndex(i) => {571				if i.question_mark_token().is_some() {572					p!(out, str("?"));573				}574				p!(out, str(".") {i.index()});575			}576			Self::SuffixIndexExpr(e) => {577				if e.question_mark_token().is_some() {578					p!(out, str(".?"));579				}580				p!(out, str("[") {e.index()} str("]"));581			}582			Self::SuffixSlice(d) => {583				p!(out, { d.slice_desc() });584			}585			Self::SuffixApply(a) => {586				p!(out, { a.args_desc() });587			}588		}589	}590}591impl Printable for Stmt {592	fn print(&self, out: &mut PrintItems) {593		match self {594			Self::StmtLocal(l) => {595				let (binds, end_comments) = children_between::<Bind>(596					l.syntax().clone(),597					l.local_kw_token().map(Into::into).as_ref(),598					l.semi_token().map(Into::into).as_ref(),599					None,600				);601				if binds.len() == 1 {602					let bind = &binds[0];603					format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);604					p!(out, str("local ") {bind.value});605				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?606				} else {607					p!(out,str("local") >i nl);608					for bind in binds {609						if bind.should_start_with_newline {610							p!(out, nl);611						}612						format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);613						p!(out, {bind.value} str(","));614						format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);615						p!(out, nl);616					}617					if end_comments.should_start_with_newline {618						p!(out, nl);619					}620					format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);621					p!(out,<i);622				}623				p!(out,str(";") nl);624			}625			Self::StmtAssert(a) => {626				p!(out, {a.assertion()} str(";") nl);627			}628		}629	}630}631impl Printable for ExprBase {632	fn print(&self, out: &mut PrintItems) {633		match self {634			Self::ExprBinary(b) => {635				p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});636			}637			Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),638			// Self::ExprSlice(s) => {639			// 	p!(new: {s.expr()} {s.slice_desc()})640			// }641			// Self::ExprIndex(i) => {642			// 	p!(new: {i.expr()} str(".") {i.index()})643			// }644			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),645			// Self::ExprApply(a) => {646			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});647			// 	if a.tailstrict_kw_token().is_some() {648			// 		p!(out,str(" tailstrict"));649			// 	}650			// 	pi651			// }652			Self::ExprObjExtend(ex) => {653				p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});654			}655			Self::ExprParened(p) => {656				p!(out, str("(") {p.expr()} str(")"));657			}658			Self::ExprString(s) => p!(out, { s.text() }),659			Self::ExprNumber(n) => p!(out, { n.number() }),660			Self::ExprArray(a) => {661				p!(out, str("[") >i nl);662				for el in a.exprs() {663					p!(out, {el} str(",") nl);664				}665				p!(out, <i str("]"));666			}667			Self::ExprObject(obj) => {668				p!(out, { obj.obj_body() });669			}670			Self::ExprArrayComp(arr) => {671				p!(out, str("[") {arr.expr()});672				for spec in arr.comp_specs() {673					p!(out, str(" ") {spec});674				}675				p!(out, str("]"));676			}677			Self::ExprImport(v) => {678				p!(out, {v.import_kind()} str(" ") {v.text()});679			}680			Self::ExprVar(n) => p!(out, { n.name() }),681			// Self::ExprLocal(l) => {682			// }683			Self::ExprIfThenElse(ite) => {684				p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});685				if ite.else_kw_token().is_some() || ite.else_().is_some() {686					p!(out, str(" else ") {ite.else_().map(|t| t.expr())});687				}688			}689			Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),690			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),691			Self::ExprError(e) => p!(out, str("error ") {e.expr()}),692			Self::ExprLiteral(l) => {693				p!(out, { l.literal() });694			}695		}696	}697}698699impl Printable for SourceFile {700	fn print(&self, out: &mut PrintItems) {701		let before = trivia_before(702			self.syntax().clone(),703			self.expr()704				.map(|e| e.syntax().clone())705				.map(Into::into)706				.as_ref(),707		);708		let after = trivia_after(709			self.syntax().clone(),710			self.expr()711				.map(|e| e.syntax().clone())712				.map(Into::into)713				.as_ref(),714		);715		format_comments(&before, CommentLocation::AboveItem, out);716		p!(out, {self.expr()} nl);717		format_comments(&after, CommentLocation::EndOfItems, out);718	}719}720721pub struct FormatOptions {722	// 0 for hard tabs723	pub indent: u8,724}725pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {726	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);727	if !errors.is_empty() {728		let mut builder = hi_doc::SnippetBuilder::new(input);729		for error in errors {730			builder731				.error(hi_doc::Text::fragment(732					format!("{:?}", error.error),733					Formatting::default(),734				))735				.range(736					error.range.start().into()737						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),738				)739				.build();740		}741		// let snippet = builder.build();742		return Err(builder);743		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip744		// ERROR rowan nodes.745		// Recovery needs to be enabled for LSP, though.746	}747	Ok(dprint_core::formatting::format(748		|| {749			let mut out = PrintItems::new();750			parsed.print(&mut out);751			out752		},753		PrintOptions {754			indent_width: if opts.indent == 0 {755				// Reasonable max length for both 2 and 4 space sized tabs.756				3757			} else {758				opts.indent759			},760			max_width: 100,761			use_tabs: opts.indent == 0,762			new_line_text: "\n",763		},764	))765}
deletedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snapdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_fmt__tests__complex_comments_snapshot.snap
+++ /dev/null
@@ -1,53 +0,0 @@
----
-source: cmds/jrsonnet-fmt/src/tests.rs
-expression: "reformat(indoc!(\"{\n\t\t  comments: {\n\t\t\t_: '',\n\t\t\t//     Plain comment\n\t\t\ta: '',\n\n\t\t\t#    Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t  },\n\t\t  comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t  },\n          spacing: {\n            a: '',\n\n            b: '',\n          },\n          noSpacing: {\n            a: '',\n            b: '',\n          },\n        }\"))"
----
-{
-	comments: {
-		_: '',
-		// Plain comment
-		a: '',
-
-		# Plain comment with empty line before
-		b: '',
-		/* Single-line multiline comment */
-		c: '',
-
-		/**
-		 * Single-line multiline doc comment
-		 */
-		c: '',
-
-		/**
-		 * Multiline doc
-		 * Comment
-		 */
-		c: '',
-
-		/*
-		Multi-line
-
-		comment
-		*/
-		d: '',
-
-		e: '', // Inline comment
-
-		k: '',
-
-		// Text after everything
-	},
-	comments2: {
-		k: '',
-		// Text after everything, but no newline above
-	},
-	spacing: {
-		a: '',
-
-		b: '',
-	},
-	noSpacing: {
-		a: '',
-		b: '',
-	},
-}
addedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__args.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__args.snap
@@ -0,0 +1,36 @@
+---
+source: crates/jrsonnet-formatter/src/tests.rs
+expression: "reformat(indoc!(\"\n\t\t\t{\n\t\t\t\tshort: aaa(1,2,3,4,5),\n\t\t\t\tlong: bbb(123123123123123123123,12312312321123123123,123123123123312123123,123123123123123123312,123123123123312321123),\n\t\t\t\tshort_in_long: bbb(aaa(1,2,3,4,5), 123123123123123123123,12312312321123123123,123123123123312123123,123123123123123123312,123123123123312321123),\n\t\t\t\tlong_in_short: aaa(1,2,3,4,5,bbb(123123123123123123123,12312312321123123123,123123123123312123123,123123123123123123312,123123123123312321123)),\n\t\t\t}\n\t\t\"))"
+---
+{
+	short: aaa(1, 2, 3, 4, 5),
+	long: bbb(
+		123123123123123123123,
+		12312312321123123123,
+		123123123123312123123,
+		123123123123123123312,
+		123123123123312321123,
+	),
+	short_in_long: bbb(
+		aaa(1, 2, 3, 4, 5),
+		123123123123123123123,
+		12312312321123123123,
+		123123123123312123123,
+		123123123123123123312,
+		123123123123312321123,
+	),
+	long_in_short: aaa(
+		1,
+		2,
+		3,
+		4,
+		5,
+		bbb(
+			123123123123123123123,
+			12312312321123123123,
+			123123123123312123123,
+			123123123123123123312,
+			123123123123312321123,
+		),
+	),
+}
addedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snapdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snap
@@ -0,0 +1,53 @@
+---
+source: crates/jrsonnet-formatter/src/tests.rs
+expression: "reformat(indoc!(\"{\n\t\t  comments: {\n\t\t\t_: '',\n\t\t\t//     Plain comment\n\t\t\ta: '',\n\n\t\t\t#    Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t  },\n\t\t  comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t  },\n          spacing: {\n            a: '',\n\n            b: '',\n          },\n          noSpacing: {\n            a: '',\n            b: '',\n          },\n        }\"))"
+---
+{
+	comments: {
+		_: '',
+		// Plain comment
+		a: '',
+
+		# Plain comment with empty line before
+		b: '',
+		/* Single-line multiline comment */
+		c: '',
+
+		/**
+		 * Single-line multiline doc comment
+		 */
+		c: '',
+
+		/**
+		 * Multiline doc
+		 * Comment
+		 */
+		c: '',
+
+		/*
+		Multi-line
+
+		comment
+		*/
+		d: '',
+
+		e: '', // Inline comment
+
+		k: '',
+
+		// Text after everything
+	},
+	comments2: {
+		k: '',
+		// Text after everything, but no newline above
+	},
+	spacing: {
+		a: '',
+
+		b: '',
+	},
+	noSpacing: {
+		a: '',
+		b: '',
+	},
+}
modifiedcrates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/tests.rs
+++ b/crates/jrsonnet-formatter/src/tests.rs
@@ -22,7 +22,7 @@
 }
 
 #[test]
-fn complex_comments_snapshot() {
+fn complex_comments() {
 	insta::assert_snapshot!(reformat(indoc!(
 		"{
 		  comments: {
@@ -77,3 +77,17 @@
         }"
 	)));
 }
+
+#[test]
+fn args() {
+	insta::assert_snapshot!(reformat(indoc!(
+		"
+			{
+				short: aaa(1,2,3,4,5),
+				long: bbb(123123123123123123123,12312312321123123123,123123123123312123123,123123123123123123312,123123123123312321123),
+				short_in_long: bbb(aaa(1,2,3,4,5), 123123123123123123123,12312312321123123123,123123123123312123123,123123123123123123312,123123123123312321123),
+				long_in_short: aaa(1,2,3,4,5,bbb(123123123123123123123,12312312321123123123,123123123123312123123,123123123123123123312,123123123123312321123)),
+			}
+		"
+	)));
+}