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 TextKind, 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;28mod tests;2930fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {31 let end_comments_items = {32 let mut items = PrintItems::new();33 if e.should_start_with_newline {34 p!(&mut items, nl);35 }36 format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);37 items.into_rc_path()38 };39 let items =40 new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();4142 let indented = with_indent(pi!(@i; nl items(items.into())));4344 pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))45}4647pub trait Printable {48 fn print(&self, out: &mut PrintItems);49}5051macro_rules! pi {52 (@i; $($t:tt)*) => {{53 #[allow(unused_mut)]54 let mut o = dprint_core::formatting::PrintItems::new();55 pi!(@s; o: $($t)*);56 o57 }};58 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{59 $o.push_string($e.to_owned());60 pi!(@s; $o: $($t)*);61 }};62 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{63 $o.push_string($e);64 pi!(@s; $o: $($t)*);65 }};66 (@s; $o:ident: nl $($t:tt)*) => {{67 $o.push_signal(dprint_core::formatting::Signal::NewLine);68 pi!(@s; $o: $($t)*);69 }};70 (@s; $o:ident: sonl $($t:tt)*) => {{71 $o.push_signal(dprint_core::formatting::Signal::SpaceOrNewLine);72 pi!(@s; $o: $($t)*);73 }};74 (@s; $o:ident: tab $($t:tt)*) => {{75 $o.push_signal(dprint_core::formatting::Signal::Tab);76 pi!(@s; $o: $($t)*);77 }};78 (@s; $o:ident: >i $($t:tt)*) => {{79 $o.push_signal(dprint_core::formatting::Signal::StartIndent);80 pi!(@s; $o: $($t)*);81 }};82 (@s; $o:ident: <i $($t:tt)*) => {{83 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);84 pi!(@s; $o: $($t)*);85 }};86 (@s; $o:ident: info($v:expr) $($t:tt)*) => {{87 $o.push_info($v);88 pi!(@s; $o: $($t)*);89 }};90 (@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{91 $o.push_anchor(LineNumberAnchor::new($v));92 pi!(@s; $o: $($t)*);93 }};94 (@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{95 $o.push_condition(dprint_core::formatting::conditions::if_true(96 $s,97 $cond.clone(),98 {99 let mut o = PrintItems::new();100 p!(o, $($i)*);101 o102 },103 ));104 pi!(@s; $o: $($t)*);105 }};106 (@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{107 $o.push_condition(dprint_core::formatting::conditions::if_true_or(108 $s,109 $cond.clone(),110 {111 let mut o = PrintItems::new();112 p!(o, $($i)*);113 o114 },115 {116 let mut o = PrintItems::new();117 p!(o, $($e)*);118 o119 },120 ));121 pi!(@s; $o: $($t)*);122 }};123 (@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{124 $o.push_condition(dprint_core::formatting::conditions::if_true_or(125 $s,126 $cond.clone(),127 {128 let o = PrintItems::new();129 o130 },131 {132 let mut o = PrintItems::new();133 p!(o, $($e)*);134 o135 },136 ));137 pi!(@s; $o: $($t)*);138 }};139 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{140 $expr.print($o);141 pi!(@s; $o: $($t)*);142 }};143 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{144 $o.extend($expr);145 pi!(@s; $o: $($t)*);146 }};147 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{148 if $e {149 pi!(@s; $o: $($then)*);150 }151 pi!(@s; $o: $($t)*);152 }};153 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{154 if $e {155 pi!(@s; $o: $($then)*);156 } else {157 pi!(@s; $o: $($else)*);158 }159 pi!(@s; $o: $($t)*);160 }};161 (@s; $i:ident:) => {}162}163macro_rules! p {164 ($o:ident, $($t:tt)*) => {165 pi!(@s; $o: $($t)*)166 };167 (&mut $o:ident, $($t:tt)*) => {168 let om = &mut $o;169 pi!(@s; om: $($t)*)170 };171}172pub(crate) use p;173pub(crate) use pi;174175impl<P> Printable for Option<P>176where177 P: Printable,178{179 fn print(&self, out: &mut PrintItems) {180 if let Some(v) = self {181 v.print(out);182 } else {183 p!(184 out,185 string(format!(186 "/*missing {}*/",187 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")188 ),)189 );190 }191 }192}193194impl Printable for SyntaxToken {195 fn print(&self, out: &mut PrintItems) {196 p!(out, string(self.to_string()));197 }198}199200impl Printable for Text {201 fn print(&self, out: &mut PrintItems) {202 if matches!(self.kind(), TextKind::StringBlock) {203 let text = self.text();204205 for (i, ele) in text.split("\n").enumerate() {206 if i != 0 {207 p!(out, nl);208 }209 210 p!(out, string(ele.to_string()));211 }212 return;213 }214 p!(out, string(format!("{}", self)));215 }216}217impl Printable for Number {218 fn print(&self, out: &mut PrintItems) {219 p!(out, string(format!("{}", self)));220 }221}222223impl Printable for Name {224 fn print(&self, out: &mut PrintItems) {225 p!(out, { self.ident_lit() });226 }227}228229impl Printable for DestructRest {230 fn print(&self, out: &mut PrintItems) {231 p!(out, str("..."));232 if let Some(name) = self.into() {233 p!(out, { name });234 }235 }236}237238impl Printable for Destruct {239 fn print(&self, out: &mut PrintItems) {240 match self {241 Self::DestructFull(f) => {242 p!(out, { f.name() });243 }244 Self::DestructSkip(_) => p!(out, str("?")),245 Self::DestructArray(a) => {246 p!(out, str("[") >i nl);247 for el in a.destruct_array_parts() {248 match el {249 DestructArrayPart::DestructArrayElement(e) => {250 p!(out, {e.destruct()} str(",") nl);251 }252 DestructArrayPart::DestructRest(d) => {253 p!(out, {d} str(",") nl);254 }255 }256 }257 p!(out, <i str("]"));258 }259 Self::DestructObject(o) => {260 p!(out, str("{") >i nl);261 for item in o.destruct_object_fields() {262 p!(out, { item.field() });263 if let Some(des) = item.destruct() {264 p!(out, str(": ") {des});265 }266 if let Some(def) = item.expr() {267 p!(out, str(" = ") {def});268 }269 p!(out, str(",") nl);270 }271 if let Some(rest) = o.destruct_rest() {272 p!(out, {rest} nl);273 }274 p!(out, <i str("}"));275 }276 }277 }278}279280impl Printable for FieldName {281 fn print(&self, out: &mut PrintItems) {282 match self {283 Self::FieldNameFixed(f) => {284 if let Some(id) = f.id() {285 p!(out, { id });286 } else if let Some(str) = f.text() {287 p!(out, { str });288 } else {289 p!(out, str("/*missing FieldName*/"));290 }291 }292 Self::FieldNameDynamic(d) => {293 p!(out, str("[") {d.expr()} str("]"));294 }295 }296 }297}298299impl Printable for Visibility {300 fn print(&self, out: &mut PrintItems) {301 p!(out, string(self.to_string()));302 }303}304305impl Printable for ObjLocal {306 fn print(&self, out: &mut PrintItems) {307 p!(out, str("local ") {self.bind()});308 }309}310311impl Printable for Assertion {312 fn print(&self, out: &mut PrintItems) {313 p!(out, str("assert ") {self.condition()});314 if self.colon_token().is_some() || self.message().is_some() {315 p!(out, str(": ") {self.message()});316 }317 }318}319320impl Printable for ParamsDesc {321 fn print(&self, out: &mut PrintItems) {322 p!(out, str("(") >i nl);323 for param in self.params() {324 p!(out, { param.destruct() });325 if param.assign_token().is_some() || param.expr().is_some() {326 p!(out, str(" = ") {param.expr()});327 }328 p!(out, str(",") nl);329 }330 p!(out, <i str(")"));331 }332}333impl Printable for ArgsDesc {334 fn print(&self, out: &mut PrintItems) {335 let start = LineNumber::new("args start line");336 let end = LineNumber::new("args end line");337 let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {338 is_multiple_lines(condition_context, start, end)339 });340341 let (children, end_comments) = children_between::<Arg>(342 self.syntax().clone(),343 self.l_paren_token().map(Into::into).as_ref(),344 self.r_paren_token().map(Into::into).as_ref(),345 None,346 );347348 fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {349 let mut _out = PrintItems::new();350 let out = &mut _out;351352 let mut args = children.into_iter().peekable();353 while let Some(ele) = args.next() {354 if ele.should_start_with_newline {355 p!(out, nl);356 }357 format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);358 let arg = ele.value;359 if arg.name().is_some() || arg.assign_token().is_some() {360 p!(out, {arg.name()} str(" = "));361 }362 p!(out, { arg.expr() });363 let has_more = args.peek().is_some();364 if has_more {365 p!(out, str(","));366 } else {367 p!(out, if("trailing comma", multi_line, str(",")));368 }369 format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);370 if has_more {371 p!(out, if_else("arg separator", multi_line, nl)(sonl));372 }373 }374 _out375 }376377 let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();378 let args_indented = with_indent(pi!(@i; nl items(args_items.into())));379380 p!(out, str("(") info(start));381 p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));382 if end_comments.should_start_with_newline {383 p!(out, nl);384 }385 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);386 p!(out, str(")") info(end));387 }388}389impl Printable for SliceDesc {390 fn print(&self, out: &mut PrintItems) {391 p!(out, str("["));392 if self.from().is_some() {393 p!(out, { self.from() });394 }395 p!(out, str(":"));396 if self.end().is_some() {397 p!(out, { self.end().map(|e| e.expr()) });398 }399 400 if self.step().is_some() {401 p!(out, str(":") {self.step().map(|e|e.expr())});402 }403 p!(out, str("]"));404 }405}406407impl Printable for Member {408 fn print(&self, out: &mut PrintItems) {409 match self {410 Self::MemberBindStmt(b) => {411 p!(out, { b.obj_local() });412 }413 Self::MemberAssertStmt(ass) => {414 p!(out, { ass.assertion() });415 }416 Self::MemberFieldNormal(n) => {417 p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});418 }419 Self::MemberFieldMethod(m) => {420 p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});421 }422 }423 }424}425426impl Printable for ObjBody {427 fn print(&self, out: &mut PrintItems) {428 match self {429 Self::ObjBodyComp(l) => {430 let (children, mut end_comments) = children_between::<Member>(431 l.syntax().clone(),432 l.l_brace_token().map(Into::into).as_ref(),433 Some(434 &(l.comp_specs()435 .next()436 .expect("at least one spec is defined")437 .syntax()438 .clone())439 .into(),440 ),441 None,442 );443 let trailing_for_comp = end_comments.extract_trailing();444 p!(out, str("{") >i nl);445 for mem in children {446 if mem.should_start_with_newline {447 p!(out, nl);448 }449 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);450 p!(out, {mem.value} str(","));451 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);452 p!(out, nl);453 }454455 if end_comments.should_start_with_newline {456 p!(out, nl);457 }458 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);459460 let (compspecs, end_comments) = children_between::<CompSpec>(461 l.syntax().clone(),462 l.member_comps()463 .last()464 .map(|m| m.syntax().clone())465 .map(Into::into)466 .or_else(|| l.l_brace_token().map(Into::into))467 .as_ref(),468 l.r_brace_token().map(Into::into).as_ref(),469 Some(trailing_for_comp),470 );471 for mem in compspecs {472 if mem.should_start_with_newline {473 p!(out, nl);474 }475 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);476 p!(out, { mem.value });477 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);478 }479 if end_comments.should_start_with_newline {480 p!(out, nl);481 }482 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);483484 p!(out, nl <i str("}"));485 }486 Self::ObjBodyMemberList(l) => {487 let (children, end_comments) = children_between::<Member>(488 l.syntax().clone(),489 l.l_brace_token().map(Into::into).as_ref(),490 l.r_brace_token().map(Into::into).as_ref(),491 None,492 );493 if children.is_empty() && end_comments.is_empty() {494 p!(out, str("{ }"));495 return;496 }497498 let source_is_multiline = children.iter().any(|c| c.triggers_multiline)499 || end_comments.should_start_with_newline;500501 let start = LineNumber::new("obj start line");502 let end = LineNumber::new("obj end line");503 let multi_line: ConditionResolver = if source_is_multiline {504 true_resolver()505 } else {506 Rc::new(move |ctx: &mut ConditionResolverContext| {507 is_multiple_lines(ctx, start, end)508 })509 };510511 fn gen_members(512 children: Vec<Child<Member>>,513 multi_line: ConditionResolver,514 ) -> PrintItems {515 let mut _out = PrintItems::new();516 let out = &mut _out;517 let mut members = children.into_iter().peekable();518 while let Some(mem) = members.next() {519 if mem.should_start_with_newline {520 p!(out, nl);521 }522 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);523 p!(out, { mem.value });524 let has_more = members.peek().is_some();525 if has_more {526 p!(out, str(","));527 } else {528 p!(out, if("trailing comma", multi_line, str(",")));529 }530 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);531 p!(out, if_else("member separator", multi_line, nl)(sonl));532 }533 _out534 }535536 let members_items =537 new_line_group(gen_members(children, multi_line.clone())).into_rc_path();538539 let members = with_indent_eoi(multi_line, members_items.into(), end_comments);540541 p!(out, str("{") info(start));542 p!(out, items(members));543 p!(out, str("}") info(end));544 }545 }546 }547}548impl Printable for UnaryOperator {549 fn print(&self, out: &mut PrintItems) {550 p!(out, string(self.text().to_string()));551 }552}553impl Printable for BinaryOperator {554 fn print(&self, out: &mut PrintItems) {555 p!(out, string(self.text().to_string()));556 }557}558impl Printable for Bind {559 fn print(&self, out: &mut PrintItems) {560 match self {561 Self::BindDestruct(d) => {562 p!(out, {d.into()} str(" = ") {d.value()});563 }564 Self::BindFunction(f) => {565 p!(out, {f.name()} {f.params()} str(" = ") {f.value()});566 }567 }568 }569}570impl Printable for Literal {571 fn print(&self, out: &mut PrintItems) {572 p!(out, string(self.syntax().to_string()));573 }574}575impl Printable for ImportKind {576 fn print(&self, out: &mut PrintItems) {577 p!(out, string(self.syntax().to_string()));578 }579}580impl Printable for ForSpec {581 fn print(&self, out: &mut PrintItems) {582 p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});583 }584}585impl Printable for IfSpec {586 fn print(&self, out: &mut PrintItems) {587 p!(out, str("if ") {self.expr()});588 }589}590impl Printable for CompSpec {591 fn print(&self, out: &mut PrintItems) {592 match self {593 Self::ForSpec(f) => f.print(out),594 Self::IfSpec(i) => i.print(out),595 }596 }597}598impl Printable for Expr {599 fn print(&self, out: &mut PrintItems) {600 let (stmts, _ending) = children_between::<Stmt>(601 self.syntax().clone(),602 None,603 self.expr_base()604 .as_ref()605 .map(ExprBase::syntax)606 .cloned()607 .map(Into::into)608 .as_ref(),609 None,610 );611 for stmt in stmts {612 p!(out, { stmt.value });613 }614 p!(out, { self.expr_base() });615 let (suffixes, _ending) = children_between::<Suffix>(616 self.syntax().clone(),617 self.expr_base()618 .as_ref()619 .map(ExprBase::syntax)620 .cloned()621 .map(Into::into)622 .as_ref(),623 None,624 None,625 );626 for suffix in suffixes {627 p!(out, { suffix.value });628 }629 }630}631impl Printable for Suffix {632 fn print(&self, out: &mut PrintItems) {633 match self {634 Self::SuffixIndex(i) => {635 if i.question_mark_token().is_some() {636 p!(out, str("?"));637 }638 p!(out, str(".") {i.index()});639 }640 Self::SuffixIndexExpr(e) => {641 if e.question_mark_token().is_some() {642 p!(out, str(".?"));643 }644 p!(out, str("[") {e.index()} str("]"));645 }646 Self::SuffixSlice(d) => {647 p!(out, { d.slice_desc() });648 }649 Self::SuffixApply(a) => {650 p!(out, { a.args_desc() });651 }652 }653 }654}655impl Printable for Stmt {656 fn print(&self, out: &mut PrintItems) {657 match self {658 Self::StmtLocal(l) => {659 let (binds, end_comments) = children_between::<Bind>(660 l.syntax().clone(),661 l.local_kw_token().map(Into::into).as_ref(),662 l.semi_token().map(Into::into).as_ref(),663 None,664 );665 if binds.len() == 1 {666 let bind = &binds[0];667 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);668 p!(out, str("local ") {bind.value});669 670 } else {671 p!(out,str("local") >i nl);672 for bind in binds {673 if bind.should_start_with_newline {674 p!(out, nl);675 }676 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);677 p!(out, {bind.value} str(","));678 format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);679 p!(out, nl);680 }681 if end_comments.should_start_with_newline {682 p!(out, nl);683 }684 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);685 p!(out,<i);686 }687 p!(out,str(";") nl);688 }689 Self::StmtAssert(a) => {690 p!(out, {a.assertion()} str(";") nl);691 }692 }693 }694}695impl Printable for ExprBase {696 fn print(&self, out: &mut PrintItems) {697 match self {698 Self::ExprBinary(b) => {699 p!(out, {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()});700 }701 Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 Self::ExprObjExtend(ex) => {717 p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});718 }719 Self::ExprParened(p) => {720 p!(out, str("(") {p.expr()} str(")"));721 }722 Self::ExprString(s) => p!(out, { s.text() }),723 Self::ExprNumber(n) => p!(out, { n.number() }),724 Self::ExprArray(a) => {725 p!(out, str("[") >i nl);726 for el in a.exprs() {727 p!(out, {el} str(",") nl);728 }729 p!(out, <i str("]"));730 }731 Self::ExprObject(obj) => {732 p!(out, { obj.obj_body() });733 }734 Self::ExprArrayComp(arr) => {735 p!(out, str("[") {arr.expr()});736 for spec in arr.comp_specs() {737 p!(out, str(" ") {spec});738 }739 p!(out, str("]"));740 }741 Self::ExprImport(v) => {742 p!(out, {v.import_kind()} str(" ") {v.text()});743 }744 Self::ExprVar(n) => p!(out, { n.name() }),745 746 747 Self::ExprIfThenElse(ite) => {748 p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});749 if ite.else_kw_token().is_some() || ite.else_().is_some() {750 p!(out, str(" else ") {ite.else_().map(|t| t.expr())});751 }752 }753 Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),754 755 Self::ExprError(e) => p!(out, str("error ") {e.expr()}),756 Self::ExprLiteral(l) => {757 p!(out, { l.literal() });758 }759 }760 }761}762763impl Printable for SourceFile {764 fn print(&self, out: &mut PrintItems) {765 let before = trivia_before(766 self.syntax().clone(),767 self.expr()768 .map(|e| e.syntax().clone())769 .map(Into::into)770 .as_ref(),771 );772 let after = trivia_after(773 self.syntax().clone(),774 self.expr()775 .map(|e| e.syntax().clone())776 .map(Into::into)777 .as_ref(),778 );779 format_comments(&before, CommentLocation::AboveItem, out);780 p!(out, {self.expr()} nl);781 format_comments(&after, CommentLocation::EndOfItems, out);782 }783}784785pub struct FormatOptions {786 787 pub indent: u8,788}789pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {790 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);791 if !errors.is_empty() {792 let mut builder = hi_doc::SnippetBuilder::new(input);793 for error in errors {794 builder795 .error(hi_doc::Text::fragment(796 format!("{:?}", error.error),797 Formatting::default(),798 ))799 .range(800 error.range.start().into()801 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),802 )803 .build();804 }805 806 return Err(builder);807 808 809 810 }811 Ok(dprint_core::formatting::format(812 || {813 let mut out = PrintItems::new();814 parsed.print(&mut out);815 out816 },817 PrintOptions {818 indent_width: if opts.indent == 0 {819 820 3821 } else {822 opts.indent823 },824 max_width: 100,825 use_tabs: opts.indent == 0,826 new_line_text: "\n",827 },828 ))829}