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 nodes::{13 Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,14 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,15 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,16 UnaryOperator, Visibility,17 },18 AstNode, AstToken as _, SyntaxToken,19};2021use crate::{22 children::{trivia_after, Child, EndingComments},23 comments::{format_comments, CommentLocation},24};2526mod children;27mod comments;28#[cfg(test)]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: info($v:expr) $($t:tt)*) => {{88 $o.push_info($v);89 pi!(@s; $o: $($t)*);90 }};91 (@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{92 $o.push_anchor(LineNumberAnchor::new($v));93 pi!(@s; $o: $($t)*);94 }};95 (@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{96 $o.push_condition(dprint_core::formatting::conditions::if_true(97 $s,98 $cond.clone(),99 {100 let mut o = PrintItems::new();101 p!(o, $($i)*);102 o103 },104 ));105 pi!(@s; $o: $($t)*);106 }};107 (@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{108 $o.push_condition(dprint_core::formatting::conditions::if_true_or(109 $s,110 $cond.clone(),111 {112 let mut o = PrintItems::new();113 p!(o, $($i)*);114 o115 },116 {117 let mut o = PrintItems::new();118 p!(o, $($e)*);119 o120 },121 ));122 pi!(@s; $o: $($t)*);123 }};124 (@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{125 $o.push_condition(dprint_core::formatting::conditions::if_true_or(126 $s,127 $cond.clone(),128 {129 let o = PrintItems::new();130 o131 },132 {133 let mut o = PrintItems::new();134 p!(o, $($e)*);135 o136 },137 ));138 pi!(@s; $o: $($t)*);139 }};140 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{141 $expr.print($o);142 pi!(@s; $o: $($t)*);143 }};144 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{145 $o.extend($expr);146 pi!(@s; $o: $($t)*);147 }};148 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{149 if $e {150 pi!(@s; $o: $($then)*);151 }152 pi!(@s; $o: $($t)*);153 }};154 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{155 if $e {156 pi!(@s; $o: $($then)*);157 } else {158 pi!(@s; $o: $($else)*);159 }160 pi!(@s; $o: $($t)*);161 }};162 (@s; $i:ident:) => {}163}164macro_rules! p {165 ($o:ident, $($t:tt)*) => {166 pi!(@s; $o: $($t)*)167 };168 (&mut $o:ident, $($t:tt)*) => {169 let om = &mut $o;170 pi!(@s; om: $($t)*)171 };172}173pub(crate) use p;174pub(crate) use pi;175176impl<P> Printable for Option<P>177where178 P: Printable,179{180 fn print(&self, out: &mut PrintItems) {181 if let Some(v) = self {182 v.print(out);183 } else {184 p!(185 out,186 string(format!(187 "/*missing {}*/",188 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")189 ),)190 );191 }192 }193}194195impl Printable for SyntaxToken {196 fn print(&self, out: &mut PrintItems) {197 p!(out, string(self.to_string()));198 }199}200201impl Printable for Text {202 fn print(&self, out: &mut PrintItems) {203 p!(out, string(format!("{}", self)));204 }205}206impl Printable for Number {207 fn print(&self, out: &mut PrintItems) {208 p!(out, string(format!("{}", self)));209 }210}211212impl Printable for Name {213 fn print(&self, out: &mut PrintItems) {214 p!(out, { self.ident_lit() });215 }216}217218impl Printable for DestructRest {219 fn print(&self, out: &mut PrintItems) {220 p!(out, str("..."));221 if let Some(name) = self.into() {222 p!(out, { name });223 }224 }225}226227impl Printable for Destruct {228 fn print(&self, out: &mut PrintItems) {229 match self {230 Self::DestructFull(f) => {231 p!(out, { f.name() });232 }233 Self::DestructSkip(_) => p!(out, str("?")),234 Self::DestructArray(a) => {235 p!(out, str("[") >i nl);236 for el in a.destruct_array_parts() {237 match el {238 DestructArrayPart::DestructArrayElement(e) => {239 p!(out, {e.destruct()} str(",") nl);240 }241 DestructArrayPart::DestructRest(d) => {242 p!(out, {d} str(",") nl);243 }244 }245 }246 p!(out, <i str("]"));247 }248 Self::DestructObject(o) => {249 p!(out, str("{") >i nl);250 for item in o.destruct_object_fields() {251 p!(out, { item.field() });252 if let Some(des) = item.destruct() {253 p!(out, str(": ") {des});254 }255 if let Some(def) = item.expr() {256 p!(out, str(" = ") {def});257 }258 p!(out, str(",") nl);259 }260 if let Some(rest) = o.destruct_rest() {261 p!(out, {rest} nl);262 }263 p!(out, <i str("}"));264 }265 }266 }267}268269impl Printable for FieldName {270 fn print(&self, out: &mut PrintItems) {271 match self {272 Self::FieldNameFixed(f) => {273 if let Some(id) = f.id() {274 p!(out, { id });275 } else if let Some(str) = f.text() {276 p!(out, { str });277 } else {278 p!(out, str("/*missing FieldName*/"));279 }280 }281 Self::FieldNameDynamic(d) => {282 p!(out, str("[") {d.expr()} str("]"));283 }284 }285 }286}287288impl Printable for Visibility {289 fn print(&self, out: &mut PrintItems) {290 p!(out, string(self.to_string()));291 }292}293294impl Printable for ObjLocal {295 fn print(&self, out: &mut PrintItems) {296 p!(out, str("local ") {self.bind()});297 }298}299300impl Printable for Assertion {301 fn print(&self, out: &mut PrintItems) {302 p!(out, str("assert ") {self.condition()});303 if self.colon_token().is_some() || self.message().is_some() {304 p!(out, str(": ") {self.message()});305 }306 }307}308309impl Printable for ParamsDesc {310 fn print(&self, out: &mut PrintItems) {311 p!(out, str("(") >i nl);312 for param in self.params() {313 p!(out, { param.destruct() });314 if param.assign_token().is_some() || param.expr().is_some() {315 p!(out, str(" = ") {param.expr()});316 }317 p!(out, str(",") nl);318 }319 p!(out, <i str(")"));320 }321}322impl Printable for ArgsDesc {323 fn print(&self, out: &mut PrintItems) {324 let start = LineNumber::new("args start line");325 let end = LineNumber::new("args end line");326 let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {327 is_multiple_lines(condition_context, start, end)328 });329330 let (children, end_comments) = children_between::<Arg>(331 self.syntax().clone(),332 self.l_paren_token().map(Into::into).as_ref(),333 self.r_paren_token().map(Into::into).as_ref(),334 None,335 );336337 fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {338 let mut _out = PrintItems::new();339 let out = &mut _out;340341 let mut args = children.into_iter().peekable();342 while let Some(ele) = args.next() {343 if ele.should_start_with_newline {344 p!(out, nl);345 }346 format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);347 let arg = ele.value;348 if arg.name().is_some() || arg.assign_token().is_some() {349 p!(out, {arg.name()} str(" = "));350 }351 p!(out, { arg.expr() });352 let has_more = args.peek().is_some();353 if has_more {354 p!(out, str(","));355 } else {356 p!(out, if("trailing comma", multi_line, str(",")));357 }358 format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);359 if has_more {360 p!(out, if_else("arg separator", multi_line, nl)(sonl));361 }362 }363 _out364 }365366 let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();367 let args_indented = with_indent(pi!(@i; nl items(args_items.into())));368369 p!(out, str("(") info(start));370 p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));371 if end_comments.should_start_with_newline {372 p!(out, nl);373 }374 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);375 p!(out, str(")") info(end));376 }377}378impl Printable for SliceDesc {379 fn print(&self, out: &mut PrintItems) {380 p!(out, str("["));381 if self.from().is_some() {382 p!(out, { self.from() });383 }384 p!(out, str(":"));385 if self.end().is_some() {386 p!(out, { self.end().map(|e| e.expr()) });387 }388 389 if self.step().is_some() {390 p!(out, str(":") {self.step().map(|e|e.expr())});391 }392 p!(out, str("]"));393 }394}395396impl Printable for Member {397 fn print(&self, out: &mut PrintItems) {398 match self {399 Self::MemberBindStmt(b) => {400 p!(out, { b.obj_local() });401 }402 Self::MemberAssertStmt(ass) => {403 p!(out, { ass.assertion() });404 }405 Self::MemberFieldNormal(n) => {406 p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});407 }408 Self::MemberFieldMethod(m) => {409 p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});410 }411 }412 }413}414415impl Printable for ObjBody {416 fn print(&self, out: &mut PrintItems) {417 match self {418 Self::ObjBodyComp(l) => {419 let (children, mut end_comments) = children_between::<Member>(420 l.syntax().clone(),421 l.l_brace_token().map(Into::into).as_ref(),422 Some(423 &(l.comp_specs()424 .next()425 .expect("at least one spec is defined")426 .syntax()427 .clone())428 .into(),429 ),430 None,431 );432 let trailing_for_comp = end_comments.extract_trailing();433 p!(out, str("{") >i nl);434 for mem in children {435 if mem.should_start_with_newline {436 p!(out, nl);437 }438 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);439 p!(out, {mem.value} str(","));440 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);441 p!(out, nl);442 }443444 if end_comments.should_start_with_newline {445 p!(out, nl);446 }447 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);448449 let (compspecs, end_comments) = children_between::<CompSpec>(450 l.syntax().clone(),451 l.member_comps()452 .last()453 .map(|m| m.syntax().clone())454 .map(Into::into)455 .or_else(|| l.l_brace_token().map(Into::into))456 .as_ref(),457 l.r_brace_token().map(Into::into).as_ref(),458 Some(trailing_for_comp),459 );460 for mem in compspecs {461 if mem.should_start_with_newline {462 p!(out, nl);463 }464 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);465 p!(out, { mem.value });466 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);467 }468 if end_comments.should_start_with_newline {469 p!(out, nl);470 }471 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);472473 p!(out, nl <i str("}"));474 }475 Self::ObjBodyMemberList(l) => {476 let (children, end_comments) = children_between::<Member>(477 l.syntax().clone(),478 l.l_brace_token().map(Into::into).as_ref(),479 l.r_brace_token().map(Into::into).as_ref(),480 None,481 );482 if children.is_empty() && end_comments.is_empty() {483 p!(out, str("{ }"));484 return;485 }486487 let source_is_multiline = children.iter().any(|c| c.triggers_multiline)488 || end_comments.should_start_with_newline;489490 let start = LineNumber::new("obj start line");491 let end = LineNumber::new("obj end line");492 let multi_line: ConditionResolver = if source_is_multiline {493 true_resolver()494 } else {495 Rc::new(move |ctx: &mut ConditionResolverContext| {496 is_multiple_lines(ctx, start, end)497 })498 };499500 fn gen_members(501 children: Vec<Child<Member>>,502 multi_line: ConditionResolver,503 ) -> PrintItems {504 let mut _out = PrintItems::new();505 let out = &mut _out;506 let mut members = children.into_iter().peekable();507 while let Some(mem) = members.next() {508 if mem.should_start_with_newline {509 p!(out, nl);510 }511 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);512 p!(out, { mem.value });513 let has_more = members.peek().is_some();514 if has_more {515 p!(out, str(","));516 } else {517 p!(out, if("trailing comma", multi_line, str(",")));518 }519 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);520 p!(out, if_else("member separator", multi_line, nl)(sonl));521 }522 _out523 }524525 let members_items =526 new_line_group(gen_members(children, multi_line.clone())).into_rc_path();527528 let members = with_indent_eoi(multi_line, members_items.into(), end_comments);529530 p!(out, str("{") info(start));531 p!(out, items(members));532 p!(out, str("}") info(end));533 }534 }535 }536}537impl Printable for UnaryOperator {538 fn print(&self, out: &mut PrintItems) {539 p!(out, string(self.text().to_string()));540 }541}542impl Printable for BinaryOperator {543 fn print(&self, out: &mut PrintItems) {544 p!(out, string(self.text().to_string()));545 }546}547impl Printable for Bind {548 fn print(&self, out: &mut PrintItems) {549 match self {550 Self::BindDestruct(d) => {551 p!(out, {d.into()} str(" = ") {d.value()});552 }553 Self::BindFunction(f) => {554 p!(out, {f.name()} {f.params()} str(" = ") {f.value()});555 }556 }557 }558}559impl Printable for Literal {560 fn print(&self, out: &mut PrintItems) {561 p!(out, string(self.syntax().to_string()));562 }563}564impl Printable for ImportKind {565 fn print(&self, out: &mut PrintItems) {566 p!(out, string(self.syntax().to_string()));567 }568}569impl Printable for ForSpec {570 fn print(&self, out: &mut PrintItems) {571 p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});572 }573}574impl Printable for IfSpec {575 fn print(&self, out: &mut PrintItems) {576 p!(out, str("if ") {self.expr()});577 }578}579impl Printable for CompSpec {580 fn print(&self, out: &mut PrintItems) {581 match self {582 Self::ForSpec(f) => f.print(out),583 Self::IfSpec(i) => i.print(out),584 }585 }586}587impl Printable for Expr {588 fn print(&self, out: &mut PrintItems) {589 let (stmts, _ending) = children_between::<Stmt>(590 self.syntax().clone(),591 None,592 self.expr_base()593 .as_ref()594 .map(ExprBase::syntax)595 .cloned()596 .map(Into::into)597 .as_ref(),598 None,599 );600 for stmt in stmts {601 p!(out, { stmt.value });602 }603 p!(out, { self.expr_base() });604 let (suffixes, _ending) = children_between::<Suffix>(605 self.syntax().clone(),606 self.expr_base()607 .as_ref()608 .map(ExprBase::syntax)609 .cloned()610 .map(Into::into)611 .as_ref(),612 None,613 None,614 );615 for suffix in suffixes {616 p!(out, { suffix.value });617 }618 }619}620impl Printable for Suffix {621 fn print(&self, out: &mut PrintItems) {622 match self {623 Self::SuffixIndex(i) => {624 if i.question_mark_token().is_some() {625 p!(out, str("?"));626 }627 p!(out, str(".") {i.index()});628 }629 Self::SuffixIndexExpr(e) => {630 if e.question_mark_token().is_some() {631 p!(out, str(".?"));632 }633 p!(out, str("[") {e.index()} str("]"));634 }635 Self::SuffixSlice(d) => {636 p!(out, { d.slice_desc() });637 }638 Self::SuffixApply(a) => {639 p!(out, { a.args_desc() });640 }641 }642 }643}644impl Printable for Stmt {645 fn print(&self, out: &mut PrintItems) {646 match self {647 Self::StmtLocal(l) => {648 let (binds, end_comments) = children_between::<Bind>(649 l.syntax().clone(),650 l.local_kw_token().map(Into::into).as_ref(),651 l.semi_token().map(Into::into).as_ref(),652 None,653 );654 if binds.len() == 1 {655 let bind = &binds[0];656 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);657 p!(out, str("local ") {bind.value});658 659 } else {660 p!(out,str("local") >i nl);661 for bind in binds {662 if bind.should_start_with_newline {663 p!(out, nl);664 }665 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);666 p!(out, {bind.value} str(","));667 format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);668 p!(out, nl);669 }670 if end_comments.should_start_with_newline {671 p!(out, nl);672 }673 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);674 p!(out,<i);675 }676 p!(out,str(";") nl);677 }678 Self::StmtAssert(a) => {679 p!(out, {a.assertion()} str(";") nl);680 }681 }682 }683}684impl Printable for ExprBase {685 fn print(&self, out: &mut PrintItems) {686 match self {687 Self::ExprBinary(b) => {688 p!(out, {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()});689 }690 Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 Self::ExprObjExtend(ex) => {706 p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});707 }708 Self::ExprParened(p) => {709 p!(out, str("(") {p.expr()} str(")"));710 }711 Self::ExprString(s) => p!(out, { s.text() }),712 Self::ExprNumber(n) => p!(out, { n.number() }),713 Self::ExprArray(a) => {714 p!(out, str("[") >i nl);715 for el in a.exprs() {716 p!(out, {el} str(",") nl);717 }718 p!(out, <i str("]"));719 }720 Self::ExprObject(obj) => {721 p!(out, { obj.obj_body() });722 }723 Self::ExprArrayComp(arr) => {724 p!(out, str("[") {arr.expr()});725 for spec in arr.comp_specs() {726 p!(out, str(" ") {spec});727 }728 p!(out, str("]"));729 }730 Self::ExprImport(v) => {731 p!(out, {v.import_kind()} str(" ") {v.text()});732 }733 Self::ExprVar(n) => p!(out, { n.name() }),734 735 736 Self::ExprIfThenElse(ite) => {737 p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});738 if ite.else_kw_token().is_some() || ite.else_().is_some() {739 p!(out, str(" else ") {ite.else_().map(|t| t.expr())});740 }741 }742 Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),743 744 Self::ExprError(e) => p!(out, str("error ") {e.expr()}),745 Self::ExprLiteral(l) => {746 p!(out, { l.literal() });747 }748 }749 }750}751752impl Printable for SourceFile {753 fn print(&self, out: &mut PrintItems) {754 let before = trivia_before(755 self.syntax().clone(),756 self.expr()757 .map(|e| e.syntax().clone())758 .map(Into::into)759 .as_ref(),760 );761 let after = trivia_after(762 self.syntax().clone(),763 self.expr()764 .map(|e| e.syntax().clone())765 .map(Into::into)766 .as_ref(),767 );768 format_comments(&before, CommentLocation::AboveItem, out);769 p!(out, {self.expr()} nl);770 format_comments(&after, CommentLocation::EndOfItems, out);771 }772}773774pub struct FormatOptions {775 776 pub indent: u8,777}778pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {779 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);780 if !errors.is_empty() {781 let mut builder = hi_doc::SnippetBuilder::new(input);782 for error in errors {783 builder784 .error(hi_doc::Text::fragment(785 format!("{:?}", error.error),786 Formatting::default(),787 ))788 .range(789 error.range.start().into()790 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),791 )792 .build();793 }794 795 return Err(builder);796 797 798 799 }800 Ok(dprint_core::formatting::format(801 || {802 let mut out = PrintItems::new();803 parsed.print(&mut out);804 out805 },806 PrintOptions {807 indent_width: if opts.indent == 0 {808 809 3810 } else {811 opts.indent812 },813 max_width: 100,814 use_tabs: opts.indent == 0,815 new_line_text: "\n",816 },817 ))818}