git.delta.rocks / jrsonnet / refs/commits / 8aaa082d581c

difftreelog

feat format arrays in one line if possible

lklpxturYaroslav Bolyukin2026-02-12parent: #d32a788.patch.diff
in: master

6 files changed

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,6	condition_resolvers::true_resolver,7	ir_helpers::{new_line_group, with_indent},8	ConditionResolver, ConditionResolverContext, LineNumber, PrintItems, PrintOptions,9};10use hi_doc::{Formatting, SnippetBuilder};11use jrsonnet_rowan_parser::{12	collect_lexed_str_block,13	nodes::{14		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15		DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,16		Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,17		TextKind, UnaryOperator, Visibility,18	},19	AstNode, AstToken as _, SyntaxToken,20};2122use crate::{23	children::{trivia_after, Child, EndingComments},24	comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29mod tests;3031fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {32	let end_comments_items = {33		let mut items = PrintItems::new();34		if e.should_start_with_newline {35			p!(&mut items, nl);36		}37		format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);38		items.into_rc_path()39	};40	let items =41		new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();4243	let indented = with_indent(pi!(@i; nl items(items.into())));4445	pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))46}4748pub trait Printable {49	fn print(&self, out: &mut PrintItems);50}5152macro_rules! pi {53	(@i; $($t:tt)*) => {{54		#[allow(unused_mut)]55		let mut o = dprint_core::formatting::PrintItems::new();56		pi!(@s; o: $($t)*);57		o58	}};59	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{60		$o.push_string($e.to_owned());61		pi!(@s; $o: $($t)*);62	}};63	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{64		$o.push_string($e);65		pi!(@s; $o: $($t)*);66	}};67	(@s; $o:ident: nl $($t:tt)*) => {{68		$o.push_signal(dprint_core::formatting::Signal::NewLine);69		pi!(@s; $o: $($t)*);70	}};71	(@s; $o:ident: sonl $($t:tt)*) => {{72		$o.push_signal(dprint_core::formatting::Signal::SpaceOrNewLine);73		pi!(@s; $o: $($t)*);74	}};75	(@s; $o:ident: tab $($t:tt)*) => {{76		$o.push_signal(dprint_core::formatting::Signal::Tab);77		pi!(@s; $o: $($t)*);78	}};79	(@s; $o:ident: >i $($t:tt)*) => {{80		$o.push_signal(dprint_core::formatting::Signal::StartIndent);81		pi!(@s; $o: $($t)*);82	}};83	(@s; $o:ident: <i $($t:tt)*) => {{84		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);85		pi!(@s; $o: $($t)*);86	}};87	(@s; $o:ident: >ii $($t:tt)*) => {{88		$o.push_signal(dprint_core::formatting::Signal::StartIgnoringIndent);89		pi!(@s; $o: $($t)*);90	}};91	(@s; $o:ident: <ii $($t:tt)*) => {{92		$o.push_signal(dprint_core::formatting::Signal::FinishIgnoringIndent);93		pi!(@s; $o: $($t)*);94	}};95	(@s; $o:ident: info($v:expr) $($t:tt)*) => {{96		$o.push_info($v);97		pi!(@s; $o: $($t)*);98	}};99	(@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{100		$o.push_anchor(LineNumberAnchor::new($v));101		pi!(@s; $o: $($t)*);102	}};103	(@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{104		$o.push_condition(dprint_core::formatting::conditions::if_true(105			$s,106			$cond.clone(),107			{108				let mut o = PrintItems::new();109				p!(o, $($i)*);110				o111			},112		));113		pi!(@s; $o: $($t)*);114	}};115	(@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{116		$o.push_condition(dprint_core::formatting::conditions::if_true_or(117			$s,118			$cond.clone(),119			{120				let mut o = PrintItems::new();121				p!(o, $($i)*);122				o123			},124			{125				let mut o = PrintItems::new();126				p!(o, $($e)*);127				o128			},129		));130		pi!(@s; $o: $($t)*);131	}};132	(@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{133		$o.push_condition(dprint_core::formatting::conditions::if_true_or(134			$s,135			$cond.clone(),136			{137				let o = PrintItems::new();138				o139			},140			{141				let mut o = PrintItems::new();142				p!(o, $($e)*);143				o144			},145		));146		pi!(@s; $o: $($t)*);147	}};148	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{149		$expr.print($o);150		pi!(@s; $o: $($t)*);151	}};152	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{153		$o.extend($expr);154		pi!(@s; $o: $($t)*);155	}};156	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{157		if $e {158			pi!(@s; $o: $($then)*);159		}160		pi!(@s; $o: $($t)*);161	}};162	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{163		if $e {164			pi!(@s; $o: $($then)*);165		} else {166			pi!(@s; $o: $($else)*);167		}168		pi!(@s; $o: $($t)*);169	}};170	(@s; $i:ident:) => {}171}172macro_rules! p {173	($o:ident, $($t:tt)*) => {174		pi!(@s; $o: $($t)*)175	};176	(&mut $o:ident, $($t:tt)*) => {177		let om = &mut $o;178		pi!(@s; om: $($t)*)179	};180}181pub(crate) use p;182pub(crate) use pi;183184impl<P> Printable for Option<P>185where186	P: Printable,187{188	fn print(&self, out: &mut PrintItems) {189		if let Some(v) = self {190			v.print(out);191		} else {192			p!(193				out,194				string(format!(195					"/*missing {}*/",196					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")197				),)198			);199		}200	}201}202203impl Printable for SyntaxToken {204	fn print(&self, out: &mut PrintItems) {205		p!(out, string(self.to_string()));206	}207}208209impl Printable for Text {210	fn print(&self, out: &mut PrintItems) {211		if matches!(self.kind(), TextKind::StringBlock) {212			let text = self.text();213			let mut text = collect_lexed_str_block(&text[3..])214				.expect("formatting is not performed on code with parsing errors");215216			if text.truncate && text.lines.ends_with(&[""]) {217				text.truncate = false;218				text.lines.pop();219			}220221			p!(out, str("|||"));222			if text.truncate {223				p!(out, str("-"));224			}225			p!(out, nl > i);226			for ele in text.lines {227				if ele.is_empty() {228					p!(out, >ii nl <ii);229				} else {230					p!(out, string(ele.to_string()) nl);231				}232			}233			p!(out, <i str("|||"));234235			return;236		}237		p!(out, string(format!("{}", self)));238	}239}240impl Printable for Number {241	fn print(&self, out: &mut PrintItems) {242		p!(out, string(format!("{}", self)));243	}244}245246impl Printable for Name {247	fn print(&self, out: &mut PrintItems) {248		p!(out, { self.ident_lit() });249	}250}251252impl Printable for DestructRest {253	fn print(&self, out: &mut PrintItems) {254		p!(out, str("..."));255		if let Some(name) = self.into() {256			p!(out, { name });257		}258	}259}260261impl Printable for Destruct {262	fn print(&self, out: &mut PrintItems) {263		match self {264			Self::DestructFull(f) => {265				p!(out, { f.name() });266			}267			Self::DestructSkip(_) => p!(out, str("?")),268			Self::DestructArray(a) => {269				p!(out, str("[") >i nl);270				for el in a.destruct_array_parts() {271					match el {272						DestructArrayPart::DestructArrayElement(e) => {273							p!(out, {e.destruct()} str(",") nl);274						}275						DestructArrayPart::DestructRest(d) => {276							p!(out, {d} str(",") nl);277						}278					}279				}280				p!(out, <i str("]"));281			}282			Self::DestructObject(o) => {283				p!(out, str("{") >i nl);284				for item in o.destruct_object_fields() {285					p!(out, { item.field() });286					if let Some(des) = item.destruct() {287						p!(out, str(": ") {des});288					}289					if let Some(def) = item.expr() {290						p!(out, str(" = ") {def});291					}292					p!(out, str(",") nl);293				}294				if let Some(rest) = o.destruct_rest() {295					p!(out, {rest} nl);296				}297				p!(out, <i str("}"));298			}299		}300	}301}302303impl Printable for FieldName {304	fn print(&self, out: &mut PrintItems) {305		match self {306			Self::FieldNameFixed(f) => {307				if let Some(id) = f.id() {308					p!(out, { id });309				} else if let Some(str) = f.text() {310					p!(out, { str });311				} else {312					p!(out, str("/*missing FieldName*/"));313				}314			}315			Self::FieldNameDynamic(d) => {316				p!(out, str("[") {d.expr()} str("]"));317			}318		}319	}320}321322impl Printable for Visibility {323	fn print(&self, out: &mut PrintItems) {324		p!(out, string(self.to_string()));325	}326}327328impl Printable for ObjLocal {329	fn print(&self, out: &mut PrintItems) {330		p!(out, str("local ") {self.bind()});331	}332}333334impl Printable for Assertion {335	fn print(&self, out: &mut PrintItems) {336		p!(out, str("assert ") {self.condition()});337		if self.colon_token().is_some() || self.message().is_some() {338			p!(out, str(": ") {self.message()});339		}340	}341}342343impl Printable for ParamsDesc {344	fn print(&self, out: &mut PrintItems) {345		p!(out, str("(") >i nl);346		for param in self.params() {347			p!(out, { param.destruct() });348			if param.assign_token().is_some() || param.expr().is_some() {349				p!(out, str(" = ") {param.expr()});350			}351			p!(out, str(",") nl);352		}353		p!(out, <i str(")"));354	}355}356impl Printable for ArgsDesc {357	fn print(&self, out: &mut PrintItems) {358		let start = LineNumber::new("args start line");359		let end = LineNumber::new("args end line");360		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {361			is_multiple_lines(condition_context, start, end)362		});363364		let (children, end_comments) = children_between::<Arg>(365			self.syntax().clone(),366			self.l_paren_token().map(Into::into).as_ref(),367			self.r_paren_token().map(Into::into).as_ref(),368			None,369		);370371		fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {372			let mut _out = PrintItems::new();373			let out = &mut _out;374375			let mut args = children.into_iter().peekable();376			while let Some(ele) = args.next() {377				if ele.should_start_with_newline {378					p!(out, nl);379				}380				format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);381				let arg = ele.value;382				if arg.name().is_some() || arg.assign_token().is_some() {383					p!(out, {arg.name()} str(" = "));384				}385				p!(out, { arg.expr() });386				let has_more = args.peek().is_some();387				if has_more {388					p!(out, str(","));389				} else {390					p!(out, if("trailing comma", multi_line, str(",")));391				}392				format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);393				if has_more {394					p!(out, if_else("arg separator", multi_line, nl)(sonl));395				}396			}397			_out398		}399400		let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();401		let args_indented = with_indent(pi!(@i; nl items(args_items.into())));402403		p!(out, str("(") info(start));404		p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));405		if end_comments.should_start_with_newline {406			p!(out, nl);407		}408		format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);409		p!(out, str(")") info(end));410	}411}412impl Printable for SliceDesc {413	fn print(&self, out: &mut PrintItems) {414		p!(out, str("["));415		if self.from().is_some() {416			p!(out, { self.from() });417		}418		p!(out, str(":"));419		if self.end().is_some() {420			p!(out, { self.end().map(|e| e.expr()) });421		}422		// Keep only one : in case if we don't need step423		if self.step().is_some() {424			p!(out, str(":") {self.step().map(|e|e.expr())});425		}426		p!(out, str("]"));427	}428}429430impl Printable for Member {431	fn print(&self, out: &mut PrintItems) {432		match self {433			Self::MemberBindStmt(b) => {434				p!(out, { b.obj_local() });435			}436			Self::MemberAssertStmt(ass) => {437				p!(out, { ass.assertion() });438			}439			Self::MemberFieldNormal(n) => {440				p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});441			}442			Self::MemberFieldMethod(m) => {443				p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});444			}445		}446	}447}448449impl Printable for ObjBody {450	fn print(&self, out: &mut PrintItems) {451		match self {452			Self::ObjBodyComp(l) => {453				let (children, mut end_comments) = children_between::<Member>(454					l.syntax().clone(),455					l.l_brace_token().map(Into::into).as_ref(),456					Some(457						&(l.comp_specs()458							.next()459							.expect("at least one spec is defined")460							.syntax()461							.clone())462						.into(),463					),464					None,465				);466				let trailing_for_comp = end_comments.extract_trailing();467				p!(out, str("{") >i nl);468				for mem in children {469					if mem.should_start_with_newline {470						p!(out, nl);471					}472					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);473					p!(out, {mem.value} str(","));474					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);475					p!(out, nl);476				}477478				if end_comments.should_start_with_newline {479					p!(out, nl);480				}481				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);482483				let (compspecs, end_comments) = children_between::<CompSpec>(484					l.syntax().clone(),485					l.member_comps()486						.last()487						.map(|m| m.syntax().clone())488						.map(Into::into)489						.or_else(|| l.l_brace_token().map(Into::into))490						.as_ref(),491					l.r_brace_token().map(Into::into).as_ref(),492					Some(trailing_for_comp),493				);494				for mem in compspecs {495					if mem.should_start_with_newline {496						p!(out, nl);497					}498					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);499					p!(out, { mem.value });500					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);501				}502				if end_comments.should_start_with_newline {503					p!(out, nl);504				}505				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);506507				p!(out, nl <i str("}"));508			}509			Self::ObjBodyMemberList(l) => {510				let (children, end_comments) = children_between::<Member>(511					l.syntax().clone(),512					l.l_brace_token().map(Into::into).as_ref(),513					l.r_brace_token().map(Into::into).as_ref(),514					None,515				);516				if children.is_empty() && end_comments.is_empty() {517					p!(out, str("{ }"));518					return;519				}520521				let source_is_multiline = children.iter().any(|c| c.triggers_multiline)522					|| end_comments.should_start_with_newline;523524				let start = LineNumber::new("obj start line");525				let end = LineNumber::new("obj end line");526				let multi_line: ConditionResolver = if source_is_multiline {527					true_resolver()528				} else {529					Rc::new(move |ctx: &mut ConditionResolverContext| {530						is_multiple_lines(ctx, start, end)531					})532				};533534				fn gen_members(535					children: Vec<Child<Member>>,536					multi_line: ConditionResolver,537				) -> PrintItems {538					let mut _out = PrintItems::new();539					let out = &mut _out;540					let mut members = children.into_iter().peekable();541					while let Some(mem) = members.next() {542						if mem.should_start_with_newline {543							p!(out, nl);544						}545						format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);546						p!(out, { mem.value });547						let has_more = members.peek().is_some();548						if has_more {549							p!(out, str(","));550						} else {551							p!(out, if("trailing comma", multi_line, str(",")));552						}553						format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);554						p!(out, if_else("member separator", multi_line, nl)(sonl));555					}556					_out557				}558559				let members_items =560					new_line_group(gen_members(children, multi_line.clone())).into_rc_path();561562				let members = with_indent_eoi(multi_line, members_items.into(), end_comments);563564				p!(out, str("{") info(start));565				p!(out, items(members));566				p!(out, str("}") info(end));567			}568		}569	}570}571impl Printable for UnaryOperator {572	fn print(&self, out: &mut PrintItems) {573		p!(out, string(self.text().to_string()));574	}575}576impl Printable for BinaryOperator {577	fn print(&self, out: &mut PrintItems) {578		p!(out, string(self.text().to_string()));579	}580}581impl Printable for Bind {582	fn print(&self, out: &mut PrintItems) {583		match self {584			Self::BindDestruct(d) => {585				p!(out, {d.into()} str(" = ") {d.value()});586			}587			Self::BindFunction(f) => {588				p!(out, {f.name()} {f.params()} str(" = ") {f.value()});589			}590		}591	}592}593impl Printable for Literal {594	fn print(&self, out: &mut PrintItems) {595		p!(out, string(self.syntax().to_string()));596	}597}598impl Printable for ImportKind {599	fn print(&self, out: &mut PrintItems) {600		p!(out, string(self.syntax().to_string()));601	}602}603impl Printable for ForSpec {604	fn print(&self, out: &mut PrintItems) {605		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});606	}607}608impl Printable for IfSpec {609	fn print(&self, out: &mut PrintItems) {610		p!(out, str("if ") {self.expr()});611	}612}613impl Printable for CompSpec {614	fn print(&self, out: &mut PrintItems) {615		match self {616			Self::ForSpec(f) => f.print(out),617			Self::IfSpec(i) => i.print(out),618		}619	}620}621impl Printable for Expr {622	fn print(&self, out: &mut PrintItems) {623		let (stmts, _ending) = children_between::<Stmt>(624			self.syntax().clone(),625			None,626			self.expr_base()627				.as_ref()628				.map(ExprBase::syntax)629				.cloned()630				.map(Into::into)631				.as_ref(),632			None,633		);634		for stmt in stmts {635			p!(out, { stmt.value });636		}637		p!(out, { self.expr_base() });638		let (suffixes, _ending) = children_between::<Suffix>(639			self.syntax().clone(),640			self.expr_base()641				.as_ref()642				.map(ExprBase::syntax)643				.cloned()644				.map(Into::into)645				.as_ref(),646			None,647			None,648		);649		for suffix in suffixes {650			p!(out, { suffix.value });651		}652	}653}654impl Printable for Suffix {655	fn print(&self, out: &mut PrintItems) {656		match self {657			Self::SuffixIndex(i) => {658				if i.question_mark_token().is_some() {659					p!(out, str("?"));660				}661				p!(out, str(".") {i.index()});662			}663			Self::SuffixIndexExpr(e) => {664				if e.question_mark_token().is_some() {665					p!(out, str(".?"));666				}667				p!(out, str("[") {e.index()} str("]"));668			}669			Self::SuffixSlice(d) => {670				p!(out, { d.slice_desc() });671			}672			Self::SuffixApply(a) => {673				p!(out, { a.args_desc() });674			}675		}676	}677}678impl Printable for Stmt {679	fn print(&self, out: &mut PrintItems) {680		match self {681			Self::StmtLocal(l) => {682				let (binds, end_comments) = children_between::<Bind>(683					l.syntax().clone(),684					l.local_kw_token().map(Into::into).as_ref(),685					l.semi_token().map(Into::into).as_ref(),686					None,687				);688				if binds.len() == 1 {689					let bind = &binds[0];690					format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);691					p!(out, str("local ") {bind.value});692				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?693				} else {694					p!(out,str("local") >i nl);695					for bind in binds {696						if bind.should_start_with_newline {697							p!(out, nl);698						}699						format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);700						p!(out, {bind.value} str(","));701						format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);702						p!(out, nl);703					}704					if end_comments.should_start_with_newline {705						p!(out, nl);706					}707					format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);708					p!(out,<i);709				}710				p!(out,str(";") nl);711			}712			Self::StmtAssert(a) => {713				p!(out, {a.assertion()} str(";") nl);714			}715		}716	}717}718impl Printable for ExprBase {719	fn print(&self, out: &mut PrintItems) {720		match self {721			Self::ExprBinary(b) => {722				p!(out, {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()});723			}724			Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),725			// Self::ExprSlice(s) => {726			// 	p!(new: {s.expr()} {s.slice_desc()})727			// }728			// Self::ExprIndex(i) => {729			// 	p!(new: {i.expr()} str(".") {i.index()})730			// }731			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),732			// Self::ExprApply(a) => {733			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});734			// 	if a.tailstrict_kw_token().is_some() {735			// 		p!(out,str(" tailstrict"));736			// 	}737			// 	pi738			// }739			Self::ExprObjExtend(ex) => {740				p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});741			}742			Self::ExprParened(p) => {743				p!(out, str("(") {p.expr()} str(")"));744			}745			Self::ExprString(s) => p!(out, { s.text() }),746			Self::ExprNumber(n) => p!(out, { n.number() }),747			Self::ExprArray(a) => {748				p!(out, str("[") >i nl);749				for el in a.exprs() {750					p!(out, {el} str(",") nl);751				}752				p!(out, <i str("]"));753			}754			Self::ExprObject(obj) => {755				p!(out, { obj.obj_body() });756			}757			Self::ExprArrayComp(arr) => {758				p!(out, str("[") {arr.expr()});759				for spec in arr.comp_specs() {760					p!(out, str(" ") {spec});761				}762				p!(out, str("]"));763			}764			Self::ExprImport(v) => {765				p!(out, {v.import_kind()} str(" ") {v.text()});766			}767			Self::ExprVar(n) => p!(out, { n.name() }),768			// Self::ExprLocal(l) => {769			// }770			Self::ExprIfThenElse(ite) => {771				p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});772				if ite.else_kw_token().is_some() || ite.else_().is_some() {773					p!(out, str(" else ") {ite.else_().map(|t| t.expr())});774				}775			}776			Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),777			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),778			Self::ExprError(e) => p!(out, str("error ") {e.expr()}),779			Self::ExprLiteral(l) => {780				p!(out, { l.literal() });781			}782		}783	}784}785786impl Printable for SourceFile {787	fn print(&self, out: &mut PrintItems) {788		let before = trivia_before(789			self.syntax().clone(),790			self.expr()791				.map(|e| e.syntax().clone())792				.map(Into::into)793				.as_ref(),794		);795		let after = trivia_after(796			self.syntax().clone(),797			self.expr()798				.map(|e| e.syntax().clone())799				.map(Into::into)800				.as_ref(),801		);802		format_comments(&before, CommentLocation::AboveItem, out);803		p!(out, {self.expr()} nl);804		format_comments(&after, CommentLocation::EndOfItems, out);805	}806}807808pub struct FormatOptions {809	// 0 for hard tabs810	pub indent: u8,811}812pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {813	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);814	if !errors.is_empty() {815		let mut builder = hi_doc::SnippetBuilder::new(input);816		for error in errors {817			builder818				.error(hi_doc::Text::fragment(819					format!("{:?}", error.error),820					Formatting::default(),821				))822				.range(823					error.range.start().into()824						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),825				)826				.build();827		}828		// let snippet = builder.build();829		return Err(builder);830		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip831		// ERROR rowan nodes.832		// Recovery needs to be enabled for LSP, though.833	}834	Ok(dprint_core::formatting::format(835		|| {836			let mut out = PrintItems::new();837			parsed.print(&mut out);838			out839		},840		PrintOptions {841			indent_width: if opts.indent == 0 {842				// Reasonable max length for both 2 and 4 space sized tabs.843				3844			} else {845				opts.indent846			},847			max_width: 100,848			use_tabs: opts.indent == 0,849			new_line_text: "\n",850		},851	))852}
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	condition_resolvers::true_resolver,7	ir_helpers::{new_line_group, with_indent},8	ConditionResolver, ConditionResolverContext, LineNumber, PrintItems, PrintOptions,9};10use hi_doc::{Formatting, SnippetBuilder};11use jrsonnet_rowan_parser::{12	collect_lexed_str_block,13	nodes::{14		Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15		DestructRest, Expr, ExprArray, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal,16		Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix,17		Text, TextKind, UnaryOperator, Visibility,18	},19	AstNode, AstToken as _, SyntaxToken,20};2122use crate::{23	children::{trivia_after, Child, EndingComments},24	comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29mod tests;3031fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {32	let end_comments_items = {33		let mut items = PrintItems::new();34		if e.should_start_with_newline {35			p!(&mut items, nl);36		}37		format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);38		items.into_rc_path()39	};40	let items =41		new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();4243	let indented = with_indent(pi!(@i; nl items(items.into())));4445	pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))46}4748pub trait Printable {49	fn print(&self, out: &mut PrintItems);50}5152macro_rules! pi {53	(@i; $($t:tt)*) => {{54		#[allow(unused_mut)]55		let mut o = dprint_core::formatting::PrintItems::new();56		pi!(@s; o: $($t)*);57		o58	}};59	(@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{60		$o.push_string($e.to_owned());61		pi!(@s; $o: $($t)*);62	}};63	(@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{64		$o.push_string($e);65		pi!(@s; $o: $($t)*);66	}};67	(@s; $o:ident: nl $($t:tt)*) => {{68		$o.push_signal(dprint_core::formatting::Signal::NewLine);69		pi!(@s; $o: $($t)*);70	}};71	(@s; $o:ident: sonl $($t:tt)*) => {{72		$o.push_signal(dprint_core::formatting::Signal::SpaceOrNewLine);73		pi!(@s; $o: $($t)*);74	}};75	(@s; $o:ident: tab $($t:tt)*) => {{76		$o.push_signal(dprint_core::formatting::Signal::Tab);77		pi!(@s; $o: $($t)*);78	}};79	(@s; $o:ident: >i $($t:tt)*) => {{80		$o.push_signal(dprint_core::formatting::Signal::StartIndent);81		pi!(@s; $o: $($t)*);82	}};83	(@s; $o:ident: <i $($t:tt)*) => {{84		$o.push_signal(dprint_core::formatting::Signal::FinishIndent);85		pi!(@s; $o: $($t)*);86	}};87	(@s; $o:ident: >ii $($t:tt)*) => {{88		$o.push_signal(dprint_core::formatting::Signal::StartIgnoringIndent);89		pi!(@s; $o: $($t)*);90	}};91	(@s; $o:ident: <ii $($t:tt)*) => {{92		$o.push_signal(dprint_core::formatting::Signal::FinishIgnoringIndent);93		pi!(@s; $o: $($t)*);94	}};95	(@s; $o:ident: info($v:expr) $($t:tt)*) => {{96		$o.push_info($v);97		pi!(@s; $o: $($t)*);98	}};99	(@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{100		$o.push_anchor(LineNumberAnchor::new($v));101		pi!(@s; $o: $($t)*);102	}};103	(@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{104		$o.push_condition(dprint_core::formatting::conditions::if_true(105			$s,106			$cond.clone(),107			{108				let mut o = PrintItems::new();109				p!(o, $($i)*);110				o111			},112		));113		pi!(@s; $o: $($t)*);114	}};115	(@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{116		$o.push_condition(dprint_core::formatting::conditions::if_true_or(117			$s,118			$cond.clone(),119			{120				let mut o = PrintItems::new();121				p!(o, $($i)*);122				o123			},124			{125				let mut o = PrintItems::new();126				p!(o, $($e)*);127				o128			},129		));130		pi!(@s; $o: $($t)*);131	}};132	(@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{133		$o.push_condition(dprint_core::formatting::conditions::if_true_or(134			$s,135			$cond.clone(),136			{137				let o = PrintItems::new();138				o139			},140			{141				let mut o = PrintItems::new();142				p!(o, $($e)*);143				o144			},145		));146		pi!(@s; $o: $($t)*);147	}};148	(@s; $o:ident: {$expr:expr} $($t:tt)*) => {{149		$expr.print($o);150		pi!(@s; $o: $($t)*);151	}};152	(@s; $o:ident: items($expr:expr) $($t:tt)*) => {{153		$o.extend($expr);154		pi!(@s; $o: $($t)*);155	}};156	(@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{157		if $e {158			pi!(@s; $o: $($then)*);159		}160		pi!(@s; $o: $($t)*);161	}};162	(@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{163		if $e {164			pi!(@s; $o: $($then)*);165		} else {166			pi!(@s; $o: $($else)*);167		}168		pi!(@s; $o: $($t)*);169	}};170	(@s; $i:ident:) => {}171}172macro_rules! p {173	($o:ident, $($t:tt)*) => {174		pi!(@s; $o: $($t)*)175	};176	(&mut $o:ident, $($t:tt)*) => {177		let om = &mut $o;178		pi!(@s; om: $($t)*)179	};180}181pub(crate) use p;182pub(crate) use pi;183184impl<P> Printable for Option<P>185where186	P: Printable,187{188	fn print(&self, out: &mut PrintItems) {189		if let Some(v) = self {190			v.print(out);191		} else {192			p!(193				out,194				string(format!(195					"/*missing {}*/",196					type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")197				),)198			);199		}200	}201}202203impl Printable for SyntaxToken {204	fn print(&self, out: &mut PrintItems) {205		p!(out, string(self.to_string()));206	}207}208209impl Printable for Text {210	fn print(&self, out: &mut PrintItems) {211		if matches!(self.kind(), TextKind::StringBlock) {212			let text = self.text();213			let mut text = collect_lexed_str_block(&text[3..])214				.expect("formatting is not performed on code with parsing errors");215216			if text.truncate && text.lines.ends_with(&[""]) {217				text.truncate = false;218				text.lines.pop();219			}220221			p!(out, str("|||"));222			if text.truncate {223				p!(out, str("-"));224			}225			p!(out, nl > i);226			for ele in text.lines {227				if ele.is_empty() {228					p!(out, >ii nl <ii);229				} else {230					p!(out, string(ele.to_string()) nl);231				}232			}233			p!(out, <i str("|||"));234235			return;236		}237		p!(out, string(format!("{}", self)));238	}239}240impl Printable for Number {241	fn print(&self, out: &mut PrintItems) {242		p!(out, string(format!("{}", self)));243	}244}245246impl Printable for Name {247	fn print(&self, out: &mut PrintItems) {248		p!(out, { self.ident_lit() });249	}250}251252impl Printable for DestructRest {253	fn print(&self, out: &mut PrintItems) {254		p!(out, str("..."));255		if let Some(name) = self.into() {256			p!(out, { name });257		}258	}259}260261impl Printable for Destruct {262	fn print(&self, out: &mut PrintItems) {263		match self {264			Self::DestructFull(f) => {265				p!(out, { f.name() });266			}267			Self::DestructSkip(_) => p!(out, str("?")),268			Self::DestructArray(a) => {269				p!(out, str("[") >i nl);270				for el in a.destruct_array_parts() {271					match el {272						DestructArrayPart::DestructArrayElement(e) => {273							p!(out, {e.destruct()} str(",") nl);274						}275						DestructArrayPart::DestructRest(d) => {276							p!(out, {d} str(",") nl);277						}278					}279				}280				p!(out, <i str("]"));281			}282			Self::DestructObject(o) => {283				p!(out, str("{") >i nl);284				for item in o.destruct_object_fields() {285					p!(out, { item.field() });286					if let Some(des) = item.destruct() {287						p!(out, str(": ") {des});288					}289					if let Some(def) = item.expr() {290						p!(out, str(" = ") {def});291					}292					p!(out, str(",") nl);293				}294				if let Some(rest) = o.destruct_rest() {295					p!(out, {rest} nl);296				}297				p!(out, <i str("}"));298			}299		}300	}301}302303impl Printable for FieldName {304	fn print(&self, out: &mut PrintItems) {305		match self {306			Self::FieldNameFixed(f) => {307				if let Some(id) = f.id() {308					p!(out, { id });309				} else if let Some(str) = f.text() {310					p!(out, { str });311				} else {312					p!(out, str("/*missing FieldName*/"));313				}314			}315			Self::FieldNameDynamic(d) => {316				p!(out, str("[") {d.expr()} str("]"));317			}318		}319	}320}321322impl Printable for Visibility {323	fn print(&self, out: &mut PrintItems) {324		p!(out, string(self.to_string()));325	}326}327328impl Printable for ObjLocal {329	fn print(&self, out: &mut PrintItems) {330		p!(out, str("local ") {self.bind()});331	}332}333334impl Printable for Assertion {335	fn print(&self, out: &mut PrintItems) {336		p!(out, str("assert ") {self.condition()});337		if self.colon_token().is_some() || self.message().is_some() {338			p!(out, str(": ") {self.message()});339		}340	}341}342343impl Printable for ParamsDesc {344	fn print(&self, out: &mut PrintItems) {345		p!(out, str("(") >i nl);346		for param in self.params() {347			p!(out, { param.destruct() });348			if param.assign_token().is_some() || param.expr().is_some() {349				p!(out, str(" = ") {param.expr()});350			}351			p!(out, str(",") nl);352		}353		p!(out, <i str(")"));354	}355}356impl Printable for ArgsDesc {357	fn print(&self, out: &mut PrintItems) {358		let start = LineNumber::new("args start line");359		let end = LineNumber::new("args end line");360		let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {361			is_multiple_lines(condition_context, start, end)362		});363364		let (children, end_comments) = children_between::<Arg>(365			self.syntax().clone(),366			self.l_paren_token().map(Into::into).as_ref(),367			self.r_paren_token().map(Into::into).as_ref(),368			None,369		);370371		fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {372			let mut _out = PrintItems::new();373			let out = &mut _out;374375			let mut args = children.into_iter().peekable();376			while let Some(ele) = args.next() {377				if ele.should_start_with_newline {378					p!(out, nl);379				}380				format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);381				let arg = ele.value;382				if arg.name().is_some() || arg.assign_token().is_some() {383					p!(out, {arg.name()} str(" = "));384				}385				p!(out, { arg.expr() });386				let has_more = args.peek().is_some();387				if has_more {388					p!(out, str(","));389				} else {390					p!(out, if("trailing comma", multi_line, str(",")));391				}392				format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);393				if has_more {394					p!(out, if_else("arg separator", multi_line, nl)(sonl));395				}396			}397			_out398		}399400		let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();401		let args_indented = with_indent(pi!(@i; nl items(args_items.into())));402403		p!(out, str("(") info(start));404		p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));405		if end_comments.should_start_with_newline {406			p!(out, nl);407		}408		format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);409		p!(out, str(")") info(end));410	}411}412impl Printable for SliceDesc {413	fn print(&self, out: &mut PrintItems) {414		p!(out, str("["));415		if self.from().is_some() {416			p!(out, { self.from() });417		}418		p!(out, str(":"));419		if self.end().is_some() {420			p!(out, { self.end().map(|e| e.expr()) });421		}422		// Keep only one : in case if we don't need step423		if self.step().is_some() {424			p!(out, str(":") {self.step().map(|e|e.expr())});425		}426		p!(out, str("]"));427	}428}429430impl Printable for Member {431	fn print(&self, out: &mut PrintItems) {432		match self {433			Self::MemberBindStmt(b) => {434				p!(out, { b.obj_local() });435			}436			Self::MemberAssertStmt(ass) => {437				p!(out, { ass.assertion() });438			}439			Self::MemberFieldNormal(n) => {440				p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});441			}442			Self::MemberFieldMethod(m) => {443				p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});444			}445		}446	}447}448449impl Printable for ObjBody {450	fn print(&self, out: &mut PrintItems) {451		match self {452			Self::ObjBodyComp(l) => {453				let (children, mut end_comments) = children_between::<Member>(454					l.syntax().clone(),455					l.l_brace_token().map(Into::into).as_ref(),456					Some(457						&(l.comp_specs()458							.next()459							.expect("at least one spec is defined")460							.syntax()461							.clone())462						.into(),463					),464					None,465				);466				let trailing_for_comp = end_comments.extract_trailing();467				p!(out, str("{") >i nl);468				for mem in children {469					if mem.should_start_with_newline {470						p!(out, nl);471					}472					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);473					p!(out, {mem.value} str(","));474					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);475					p!(out, nl);476				}477478				if end_comments.should_start_with_newline {479					p!(out, nl);480				}481				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);482483				let (compspecs, end_comments) = children_between::<CompSpec>(484					l.syntax().clone(),485					l.member_comps()486						.last()487						.map(|m| m.syntax().clone())488						.map(Into::into)489						.or_else(|| l.l_brace_token().map(Into::into))490						.as_ref(),491					l.r_brace_token().map(Into::into).as_ref(),492					Some(trailing_for_comp),493				);494				for mem in compspecs {495					if mem.should_start_with_newline {496						p!(out, nl);497					}498					format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);499					p!(out, { mem.value });500					format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);501				}502				if end_comments.should_start_with_newline {503					p!(out, nl);504				}505				format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);506507				p!(out, nl <i str("}"));508			}509			Self::ObjBodyMemberList(l) => {510				let (children, end_comments) = children_between::<Member>(511					l.syntax().clone(),512					l.l_brace_token().map(Into::into).as_ref(),513					l.r_brace_token().map(Into::into).as_ref(),514					None,515				);516				if children.is_empty() && end_comments.is_empty() {517					p!(out, str("{ }"));518					return;519				}520521				let source_is_multiline = children.iter().any(|c| c.triggers_multiline)522					|| end_comments.should_start_with_newline;523524				let start = LineNumber::new("obj start line");525				let end = LineNumber::new("obj end line");526				let multi_line: ConditionResolver = if source_is_multiline {527					true_resolver()528				} else {529					Rc::new(move |ctx: &mut ConditionResolverContext| {530						is_multiple_lines(ctx, start, end)531					})532				};533534				fn gen_members(535					children: Vec<Child<Member>>,536					multi_line: ConditionResolver,537				) -> PrintItems {538					let mut _out = PrintItems::new();539					let out = &mut _out;540					let mut members = children.into_iter().peekable();541					while let Some(mem) = members.next() {542						if mem.should_start_with_newline {543							p!(out, nl);544						}545						format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);546						p!(out, { mem.value });547						let has_more = members.peek().is_some();548						if has_more {549							p!(out, str(","));550						} else {551							p!(out, if("trailing comma", multi_line, str(",")));552						}553						format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);554						p!(out, if_else("member separator", multi_line, nl)(sonl));555					}556					_out557				}558559				let members_items =560					new_line_group(gen_members(children, multi_line.clone())).into_rc_path();561562				let members = with_indent_eoi(multi_line, members_items.into(), end_comments);563564				p!(out, str("{") info(start));565				p!(out, items(members));566				p!(out, str("}") info(end));567			}568		}569	}570}571impl Printable for UnaryOperator {572	fn print(&self, out: &mut PrintItems) {573		p!(out, string(self.text().to_string()));574	}575}576impl Printable for BinaryOperator {577	fn print(&self, out: &mut PrintItems) {578		p!(out, string(self.text().to_string()));579	}580}581impl Printable for Bind {582	fn print(&self, out: &mut PrintItems) {583		match self {584			Self::BindDestruct(d) => {585				p!(out, {d.into()} str(" = ") {d.value()});586			}587			Self::BindFunction(f) => {588				p!(out, {f.name()} {f.params()} str(" = ") {f.value()});589			}590		}591	}592}593impl Printable for Literal {594	fn print(&self, out: &mut PrintItems) {595		p!(out, string(self.syntax().to_string()));596	}597}598impl Printable for ImportKind {599	fn print(&self, out: &mut PrintItems) {600		p!(out, string(self.syntax().to_string()));601	}602}603impl Printable for ForSpec {604	fn print(&self, out: &mut PrintItems) {605		p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});606	}607}608impl Printable for IfSpec {609	fn print(&self, out: &mut PrintItems) {610		p!(out, str("if ") {self.expr()});611	}612}613impl Printable for CompSpec {614	fn print(&self, out: &mut PrintItems) {615		match self {616			Self::ForSpec(f) => f.print(out),617			Self::IfSpec(i) => i.print(out),618		}619	}620}621impl Printable for Expr {622	fn print(&self, out: &mut PrintItems) {623		let (stmts, _ending) = children_between::<Stmt>(624			self.syntax().clone(),625			None,626			self.expr_base()627				.as_ref()628				.map(ExprBase::syntax)629				.cloned()630				.map(Into::into)631				.as_ref(),632			None,633		);634		for stmt in stmts {635			p!(out, { stmt.value });636		}637		p!(out, { self.expr_base() });638		let (suffixes, _ending) = children_between::<Suffix>(639			self.syntax().clone(),640			self.expr_base()641				.as_ref()642				.map(ExprBase::syntax)643				.cloned()644				.map(Into::into)645				.as_ref(),646			None,647			None,648		);649		for suffix in suffixes {650			p!(out, { suffix.value });651		}652	}653}654impl Printable for Suffix {655	fn print(&self, out: &mut PrintItems) {656		match self {657			Self::SuffixIndex(i) => {658				if i.question_mark_token().is_some() {659					p!(out, str("?"));660				}661				p!(out, str(".") {i.index()});662			}663			Self::SuffixIndexExpr(e) => {664				if e.question_mark_token().is_some() {665					p!(out, str(".?"));666				}667				p!(out, str("[") {e.index()} str("]"));668			}669			Self::SuffixSlice(d) => {670				p!(out, { d.slice_desc() });671			}672			Self::SuffixApply(a) => {673				p!(out, { a.args_desc() });674			}675		}676	}677}678impl Printable for Stmt {679	fn print(&self, out: &mut PrintItems) {680		match self {681			Self::StmtLocal(l) => {682				let (binds, end_comments) = children_between::<Bind>(683					l.syntax().clone(),684					l.local_kw_token().map(Into::into).as_ref(),685					l.semi_token().map(Into::into).as_ref(),686					None,687				);688				if binds.len() == 1 {689					let bind = &binds[0];690					format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);691					p!(out, str("local ") {bind.value});692				// TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?693				} else {694					p!(out,str("local") >i nl);695					for bind in binds {696						if bind.should_start_with_newline {697							p!(out, nl);698						}699						format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);700						p!(out, {bind.value} str(","));701						format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);702						p!(out, nl);703					}704					if end_comments.should_start_with_newline {705						p!(out, nl);706					}707					format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);708					p!(out,<i);709				}710				p!(out,str(";") nl);711			}712			Self::StmtAssert(a) => {713				p!(out, {a.assertion()} str(";") nl);714			}715		}716	}717}718719impl Printable for ExprArray {720	fn print(&self, out: &mut PrintItems) {721		let (children, end_comments) = children_between::<Expr>(722			self.syntax().clone(),723			self.l_brack_token().map(Into::into).as_ref(),724			self.r_brack_token().map(Into::into).as_ref(),725			None,726		);727		if children.is_empty() && end_comments.is_empty() {728			p!(out, str("[ ]"));729			return;730		}731732		let source_is_multiline =733			children.iter().any(|c| c.triggers_multiline) || end_comments.should_start_with_newline;734735		let start = LineNumber::new("arr start line");736		let end = LineNumber::new("arr end line");737		let multi_line: ConditionResolver = if source_is_multiline {738			true_resolver()739		} else {740			Rc::new(move |ctx: &mut ConditionResolverContext| is_multiple_lines(ctx, start, end))741		};742743		fn gen_elements(children: Vec<Child<Expr>>, multi_line: ConditionResolver) -> PrintItems {744			let mut _out = PrintItems::new();745			let out = &mut _out;746			let mut els = children.into_iter().peekable();747			while let Some(el) = els.next() {748				if el.should_start_with_newline {749					p!(out, nl);750				}751				format_comments(&el.before_trivia, CommentLocation::AboveItem, out);752				p!(out, { el.value });753				let has_more = els.peek().is_some();754				if has_more {755					p!(out, str(","));756				} else {757					p!(out, if("trailing comma", multi_line, str(",")));758				}759				format_comments(&el.inline_trivia, CommentLocation::ItemInline, out);760				p!(out, if_else("element separator", multi_line, nl)(sonl))761			}762			_out763		}764765		let els_items = new_line_group(gen_elements(children, multi_line.clone())).into_rc_path();766767		let els = with_indent_eoi(multi_line, els_items.into(), end_comments);768769		p!(out, str("[") info(start) items(els) str("]") info(end));770	}771}772773impl Printable for ExprBase {774	fn print(&self, out: &mut PrintItems) {775		match self {776			Self::ExprBinary(b) => {777				p!(out, {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()});778			}779			Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),780			// Self::ExprSlice(s) => {781			// 	p!(new: {s.expr()} {s.slice_desc()})782			// }783			// Self::ExprIndex(i) => {784			// 	p!(new: {i.expr()} str(".") {i.index()})785			// }786			// Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),787			// Self::ExprApply(a) => {788			// 	let mut pi = p!(new: {a.expr()} {a.args_desc()});789			// 	if a.tailstrict_kw_token().is_some() {790			// 		p!(out,str(" tailstrict"));791			// 	}792			// 	pi793			// }794			Self::ExprObjExtend(ex) => {795				p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});796			}797			Self::ExprParened(p) => {798				p!(out, str("(") {p.expr()} str(")"));799			}800			Self::ExprString(s) => p!(out, { s.text() }),801			Self::ExprNumber(n) => p!(out, { n.number() }),802			Self::ExprArray(a) => {803				p!(out, { a })804			}805			Self::ExprObject(obj) => {806				p!(out, { obj.obj_body() });807			}808			Self::ExprArrayComp(arr) => {809				p!(out, str("[") {arr.expr()});810				for spec in arr.comp_specs() {811					p!(out, str(" ") {spec});812				}813				p!(out, str("]"));814			}815			Self::ExprImport(v) => {816				p!(out, {v.import_kind()} str(" ") {v.text()});817			}818			Self::ExprVar(n) => p!(out, { n.name() }),819			// Self::ExprLocal(l) => {820			// }821			Self::ExprIfThenElse(ite) => {822				p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});823				if ite.else_kw_token().is_some() || ite.else_().is_some() {824					p!(out, str(" else ") {ite.else_().map(|t| t.expr())});825				}826			}827			Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),828			// Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),829			Self::ExprError(e) => p!(out, str("error ") {e.expr()}),830			Self::ExprLiteral(l) => {831				p!(out, { l.literal() });832			}833		}834	}835}836837impl Printable for SourceFile {838	fn print(&self, out: &mut PrintItems) {839		let before = trivia_before(840			self.syntax().clone(),841			self.expr()842				.map(|e| e.syntax().clone())843				.map(Into::into)844				.as_ref(),845		);846		let after = trivia_after(847			self.syntax().clone(),848			self.expr()849				.map(|e| e.syntax().clone())850				.map(Into::into)851				.as_ref(),852		);853		format_comments(&before, CommentLocation::AboveItem, out);854		p!(out, {self.expr()} nl);855		format_comments(&after, CommentLocation::EndOfItems, out);856	}857}858859pub struct FormatOptions {860	// 0 for hard tabs861	pub indent: u8,862}863pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {864	let (parsed, errors) = jrsonnet_rowan_parser::parse(input);865	if !errors.is_empty() {866		let mut builder = hi_doc::SnippetBuilder::new(input);867		for error in errors {868			builder869				.error(hi_doc::Text::fragment(870					format!("{:?}", error.error),871					Formatting::default(),872				))873				.range(874					error.range.start().into()875						..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),876				)877				.build();878		}879		// let snippet = builder.build();880		return Err(builder);881		// It is possible to recover from this failure, but the output may be broken, as formatter is free to skip882		// ERROR rowan nodes.883		// Recovery needs to be enabled for LSP, though.884	}885	Ok(dprint_core::formatting::format(886		|| {887			let mut out = PrintItems::new();888			parsed.print(&mut out);889			out890		},891		PrintOptions {892			indent_width: if opts.indent == 0 {893				// Reasonable max length for both 2 and 4 space sized tabs.894				3895			} else {896				opts.indent897			},898			max_width: 100,899			use_tabs: opts.indent == 0,900			new_line_text: "\n",901		},902	))903}
modifiedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@basic_array.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@basic_array.jsonnet.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@basic_array.jsonnet.snap
@@ -3,10 +3,4 @@
 expression: reformat(&input)
 input_file: crates/jrsonnet-formatter/src/tests/basic_array.jsonnet
 ---
-[
-   1,
-   2,
-   3,
-   4,
-   5,
-]
+[ 1, 2, 3, 4, 5 ]
modifiedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@complex_nested.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@complex_nested.jsonnet.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@complex_nested.jsonnet.snap
@@ -22,9 +22,7 @@
                      {
                         name: 'myapp',
                         image: 'myapp:latest',
-                        ports: [
-                           { containerPort: 8080 },
-                        ],
+                        ports: [ { containerPort: 8080 } ],
                         env: [
                            { name: 'FOO', value: 'bar' },
                            {
modifiedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@comprehensions.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@comprehensions.jsonnet.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@comprehensions.jsonnet.snap
@@ -4,26 +4,10 @@
 input_file: crates/jrsonnet-formatter/src/tests/comprehensions.jsonnet
 ---
 {
-   arr: [x for x in [
-      1,
-      2,
-      3,
-   ]],
-   filtered: [x for x in [
-      1,
-      2,
-      3,
-      4,
-      5,
-   ] if x > 2],
+   arr: [x for x in [ 1, 2, 3 ]],
+   filtered: [x for x in [ 1, 2, 3, 4, 5 ] if x > 2],
    obj: {
       [k]: v,
-      for k in [
-         'a',
-         'b',
-      ]for v in [
-         1,
-         2,
-      ]
+      for k in [ 'a', 'b' ]for v in [ 1, 2 ]
    },
 }
modifiedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@operators.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@operators.jsonnet.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@operators.jsonnet.snap
@@ -8,11 +8,5 @@
    comparison: 1 < 2 && 3 > 2 || false,
    string_concat: 'hello' + ' ' + 'world',
    object_concat: { a: 1 } + { b: 2 },
-   array_concat: [
-      1,
-      2,
-   ] + [
-      3,
-      4,
-   ],
+   array_concat: [ 1, 2 ] + [ 3, 4 ],
 }
modifiedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@std_functions.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@std_functions.jsonnet.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@std_functions.jsonnet.snap
@@ -4,19 +4,8 @@
 input_file: crates/jrsonnet-formatter/src/tests/std_functions.jsonnet
 ---
 {
-   length: std.length(
-      [
-         1,
-         2,
-         3,
-      ],
-   ),
+   length: std.length([ 1, 2, 3 ]),
    type: std.type('hello'),
-   format: std.format(
-      'Hello, %s!',
-      [
-         'world',
-      ],
-   ),
+   format: std.format('Hello, %s!', [ 'world' ]),
    manifest: std.manifestJsonEx({ foo: 'bar' }, '  '),
 }