git.delta.rocks / jrsonnet / refs/commits / 9b1cb43cbd7b

difftreelog

source

crates/jrsonnet-formatter/src/lib.rs19.3 KiBsourcehistory
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}