difftreelog
feat format arrays in one line if possible
in: master
6 files changed
crates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth1use 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}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}crates/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 ]
crates/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' },
{
crates/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 ]
},
}
crates/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 ],
}
crates/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' }, ' '),
}