difftreelog
feat(fmt) single-line objects
in: master
6 files changed
crates/jrsonnet-formatter/src/children.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/children.rs
+++ b/crates/jrsonnet-formatter/src/children.rs
@@ -80,14 +80,19 @@
)
}
-pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {
- count_newlines_before(tt)
- + prev_inline
- .map(count_newlines_after)
- .unwrap_or_default()
+// Should start, triggers multi-line render
+pub fn should_start_with_newline(
+ prev_inline: Option<&ChildTrivia>,
+ tt: &ChildTrivia,
+) -> (bool, bool) {
+ let count =
+ count_newlines_before(tt) + prev_inline.map(count_newlines_after).unwrap_or_default();
+ (
// First for previous item end, second for current item
- >= 2
+ count >= 2,
+ count >= 1,
+ )
}
fn count_newlines_before(tt: &ChildTrivia) -> usize {
@@ -147,13 +152,14 @@
} else {
mem::take(&mut next)
};
+ let (should_start_with_newline, triggers_multiline) = should_start_with_newline(
+ current_child.as_ref().map(|c| &c.inline_trivia),
+ &before_trivia,
+ );
let last_child = current_child.replace(Child {
// First item should not start with newline
- should_start_with_newline: had_some
- && should_start_with_newline(
- current_child.as_ref().map(|c| &c.inline_trivia),
- &before_trivia,
- ),
+ should_start_with_newline: had_some && should_start_with_newline,
+ triggers_multiline,
before_trivia,
value,
inline_trivia: Vec::new(),
@@ -203,7 +209,8 @@
should_start_with_newline: should_start_with_newline(
current_child.as_ref().map(|c| &c.inline_trivia),
&next,
- ),
+ )
+ .0,
trivia: next,
};
@@ -234,6 +241,9 @@
/// item2,
/// ```
pub inline_trivia: ChildTrivia,
+ /// Is this child has whitespace that is considered significant, meaning
+ /// user has inserted it to split the value into multiple lines.
+ pub triggers_multiline: bool,
}
pub struct EndingComments {
crates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/comments.rs
+++ b/crates/jrsonnet-formatter/src/comments.rs
@@ -72,7 +72,10 @@
if matches!(loc, CommentLocation::ItemInline) {
p!(out, str(" "));
}
- p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl);
+ p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */"));
+ if matches!(loc, CommentLocation::AboveItem | CommentLocation::EndOfItems) {
+ p!(out, nl);
+ }
} else if !lines.is_empty() {
fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
let offset = a
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 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}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, PrintItemPath, PrintItems,9 PrintOptions, Signal,10};11use hi_doc::{Formatting, SnippetBuilder};12use jrsonnet_rowan_parser::{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 Trivia, TriviaKind, 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;29#[cfg(test)]30mod tests;3132fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {33 let end_comments_items = {34 let mut items = PrintItems::new();35 if e.should_start_with_newline {36 p!(&mut items, nl);37 }38 format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);39 items.into_rc_path()40 };41 let items =42 new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();4344 let indented = with_indent(pi!(@i; nl items(items.into())));4546 pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))47}4849pub trait Printable {50 fn print(&self, out: &mut PrintItems);51}5253macro_rules! pi {54 (@i; $($t:tt)*) => {{55 #[allow(unused_mut)]56 let mut o = dprint_core::formatting::PrintItems::new();57 pi!(@s; o: $($t)*);58 o59 }};60 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{61 $o.push_string($e.to_owned());62 pi!(@s; $o: $($t)*);63 }};64 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{65 $o.push_string($e);66 pi!(@s; $o: $($t)*);67 }};68 (@s; $o:ident: nl $($t:tt)*) => {{69 $o.push_signal(dprint_core::formatting::Signal::NewLine);70 pi!(@s; $o: $($t)*);71 }};72 (@s; $o:ident: sonl $($t:tt)*) => {{73 $o.push_signal(dprint_core::formatting::Signal::SpaceOrNewLine);74 pi!(@s; $o: $($t)*);75 }};76 (@s; $o:ident: tab $($t:tt)*) => {{77 $o.push_signal(dprint_core::formatting::Signal::Tab);78 pi!(@s; $o: $($t)*);79 }};80 (@s; $o:ident: >i $($t:tt)*) => {{81 $o.push_signal(dprint_core::formatting::Signal::StartIndent);82 pi!(@s; $o: $($t)*);83 }};84 (@s; $o:ident: <i $($t:tt)*) => {{85 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);86 pi!(@s; $o: $($t)*);87 }};88 (@s; $o:ident: info($v:expr) $($t:tt)*) => {{89 $o.push_info($v);90 pi!(@s; $o: $($t)*);91 }};92 (@s; $o:ident: ln_anchor($v:expr) $($t:tt)*) => {{93 $o.push_anchor(LineNumberAnchor::new($v));94 pi!(@s; $o: $($t)*);95 }};96 (@s; $o:ident: if($s:literal, $cond:expr, $($i:tt)*) $($t:tt)*) => {{97 $o.push_condition(dprint_core::formatting::conditions::if_true(98 $s,99 $cond.clone(),100 {101 let mut o = PrintItems::new();102 p!(o, $($i)*);103 o104 },105 ));106 pi!(@s; $o: $($t)*);107 }};108 (@s; $o:ident: if_else($s:literal, $cond:expr, $($i:tt)*)($($e:tt)+) $($t:tt)*) => {{109 $o.push_condition(dprint_core::formatting::conditions::if_true_or(110 $s,111 $cond.clone(),112 {113 let mut o = PrintItems::new();114 p!(o, $($i)*);115 o116 },117 {118 let mut o = PrintItems::new();119 p!(o, $($e)*);120 o121 },122 ));123 pi!(@s; $o: $($t)*);124 }};125 (@s; $o:ident: if_not($s:literal, $cond:expr, $($e:tt)*) $($t:tt)*) => {{126 $o.push_condition(dprint_core::formatting::conditions::if_true_or(127 $s,128 $cond.clone(),129 {130 let o = PrintItems::new();131 o132 },133 {134 let mut o = PrintItems::new();135 p!(o, $($e)*);136 o137 },138 ));139 pi!(@s; $o: $($t)*);140 }};141 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{142 $expr.print($o);143 pi!(@s; $o: $($t)*);144 }};145 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{146 $o.extend($expr);147 pi!(@s; $o: $($t)*);148 }};149 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{150 if $e {151 pi!(@s; $o: $($then)*);152 }153 pi!(@s; $o: $($t)*);154 }};155 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{156 if $e {157 pi!(@s; $o: $($then)*);158 } else {159 pi!(@s; $o: $($else)*);160 }161 pi!(@s; $o: $($t)*);162 }};163 (@s; $i:ident:) => {}164}165macro_rules! p {166 ($o:ident, $($t:tt)*) => {167 pi!(@s; $o: $($t)*)168 };169 (&mut $o:ident, $($t:tt)*) => {170 let om = &mut $o;171 pi!(@s; om: $($t)*)172 };173}174pub(crate) use p;175pub(crate) use pi;176177impl<P> Printable for Option<P>178where179 P: Printable,180{181 fn print(&self, out: &mut PrintItems) {182 if let Some(v) = self {183 v.print(out);184 } else {185 p!(186 out,187 string(format!(188 "/*missing {}*/",189 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")190 ),)191 );192 }193 }194}195196impl Printable for SyntaxToken {197 fn print(&self, out: &mut PrintItems) {198 p!(out, string(self.to_string()));199 }200}201202impl Printable for Text {203 fn print(&self, out: &mut PrintItems) {204 p!(out, string(format!("{}", self)));205 }206}207impl Printable for Number {208 fn print(&self, out: &mut PrintItems) {209 p!(out, string(format!("{}", self)));210 }211}212213impl Printable for Name {214 fn print(&self, out: &mut PrintItems) {215 p!(out, { self.ident_lit() });216 }217}218219impl Printable for DestructRest {220 fn print(&self, out: &mut PrintItems) {221 p!(out, str("..."));222 if let Some(name) = self.into() {223 p!(out, { name });224 }225 }226}227228impl Printable for Destruct {229 fn print(&self, out: &mut PrintItems) {230 match self {231 Self::DestructFull(f) => {232 p!(out, { f.name() });233 }234 Self::DestructSkip(_) => p!(out, str("?")),235 Self::DestructArray(a) => {236 p!(out, str("[") >i nl);237 for el in a.destruct_array_parts() {238 match el {239 DestructArrayPart::DestructArrayElement(e) => {240 p!(out, {e.destruct()} str(",") nl);241 }242 DestructArrayPart::DestructRest(d) => {243 p!(out, {d} str(",") nl);244 }245 }246 }247 p!(out, <i str("]"));248 }249 Self::DestructObject(o) => {250 p!(out, str("{") >i nl);251 for item in o.destruct_object_fields() {252 p!(out, { item.field() });253 if let Some(des) = item.destruct() {254 p!(out, str(": ") {des});255 }256 if let Some(def) = item.expr() {257 p!(out, str(" = ") {def});258 }259 p!(out, str(",") nl);260 }261 if let Some(rest) = o.destruct_rest() {262 p!(out, {rest} nl);263 }264 p!(out, <i str("}"));265 }266 }267 }268}269270impl Printable for FieldName {271 fn print(&self, out: &mut PrintItems) {272 match self {273 Self::FieldNameFixed(f) => {274 if let Some(id) = f.id() {275 p!(out, { id });276 } else if let Some(str) = f.text() {277 p!(out, { str });278 } else {279 p!(out, str("/*missing FieldName*/"));280 }281 }282 Self::FieldNameDynamic(d) => {283 p!(out, str("[") {d.expr()} str("]"));284 }285 }286 }287}288289impl Printable for Visibility {290 fn print(&self, out: &mut PrintItems) {291 p!(out, string(self.to_string()));292 }293}294295impl Printable for ObjLocal {296 fn print(&self, out: &mut PrintItems) {297 p!(out, str("local ") {self.bind()});298 }299}300301impl Printable for Assertion {302 fn print(&self, out: &mut PrintItems) {303 p!(out, str("assert ") {self.condition()});304 if self.colon_token().is_some() || self.message().is_some() {305 p!(out, str(": ") {self.message()});306 }307 }308}309310impl Printable for ParamsDesc {311 fn print(&self, out: &mut PrintItems) {312 p!(out, str("(") >i nl);313 for param in self.params() {314 p!(out, { param.destruct() });315 if param.assign_token().is_some() || param.expr().is_some() {316 p!(out, str(" = ") {param.expr()});317 }318 p!(out, str(",") nl);319 }320 p!(out, <i str(")"));321 }322}323impl Printable for ArgsDesc {324 fn print(&self, out: &mut PrintItems) {325 let start = LineNumber::new("args start line");326 let end = LineNumber::new("args end line");327 let multi_line = Rc::new(move |condition_context: &mut ConditionResolverContext| {328 is_multiple_lines(condition_context, start, end)329 });330331 let (children, end_comments) = children_between::<Arg>(332 self.syntax().clone(),333 self.l_paren_token().map(Into::into).as_ref(),334 self.r_paren_token().map(Into::into).as_ref(),335 None,336 );337338 fn gen_args(children: Vec<Child<Arg>>, multi_line: ConditionResolver) -> PrintItems {339 let mut _out = PrintItems::new();340 let out = &mut _out;341342 let mut args = children.into_iter().peekable();343 while let Some(ele) = args.next() {344 if ele.should_start_with_newline {345 p!(out, nl);346 }347 format_comments(&ele.before_trivia, CommentLocation::AboveItem, out);348 let arg = ele.value;349 if arg.name().is_some() || arg.assign_token().is_some() {350 p!(out, {arg.name()} str(" = "));351 }352 p!(out, { arg.expr() });353 let has_more = args.peek().is_some();354 if has_more {355 p!(out, str(","));356 } else {357 p!(out, if("trailing comma", multi_line, str(",")));358 }359 format_comments(&ele.inline_trivia, CommentLocation::ItemInline, out);360 if has_more {361 p!(out, if_else("arg separator", multi_line, nl)(sonl));362 }363 }364 _out365 }366367 let args_items = new_line_group(gen_args(children, multi_line.clone())).into_rc_path();368 let args_indented = with_indent(pi!(@i; nl items(args_items.into())));369370 p!(out, str("(") info(start));371 p!(out, if_else("args body", multi_line, items(args_indented) nl)(items(args_items.into())));372 if end_comments.should_start_with_newline {373 p!(out, nl);374 }375 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);376 p!(out, str(")") info(end));377 }378}379impl Printable for SliceDesc {380 fn print(&self, out: &mut PrintItems) {381 p!(out, str("["));382 if self.from().is_some() {383 p!(out, { self.from() });384 }385 p!(out, str(":"));386 if self.end().is_some() {387 p!(out, { self.end().map(|e| e.expr()) });388 }389 // Keep only one : in case if we don't need step390 if self.step().is_some() {391 p!(out, str(":") {self.step().map(|e|e.expr())});392 }393 p!(out, str("]"));394 }395}396397impl Printable for Member {398 fn print(&self, out: &mut PrintItems) {399 match self {400 Self::MemberBindStmt(b) => {401 p!(out, { b.obj_local() });402 }403 Self::MemberAssertStmt(ass) => {404 p!(out, { ass.assertion() });405 }406 Self::MemberFieldNormal(n) => {407 p!(out, {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()});408 }409 Self::MemberFieldMethod(m) => {410 p!(out, {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()});411 }412 }413 }414}415416impl Printable for ObjBody {417 fn print(&self, out: &mut PrintItems) {418 match self {419 Self::ObjBodyComp(l) => {420 let (children, mut end_comments) = children_between::<Member>(421 l.syntax().clone(),422 l.l_brace_token().map(Into::into).as_ref(),423 Some(424 &(l.comp_specs()425 .next()426 .expect("at least one spec is defined")427 .syntax()428 .clone())429 .into(),430 ),431 None,432 );433 let trailing_for_comp = end_comments.extract_trailing();434 p!(out, str("{") >i nl);435 for mem in children {436 if mem.should_start_with_newline {437 p!(out, nl);438 }439 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);440 p!(out, {mem.value} str(","));441 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);442 p!(out, nl);443 }444445 if end_comments.should_start_with_newline {446 p!(out, nl);447 }448 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);449450 let (compspecs, end_comments) = children_between::<CompSpec>(451 l.syntax().clone(),452 l.member_comps()453 .last()454 .map(|m| m.syntax().clone())455 .map(Into::into)456 .or_else(|| l.l_brace_token().map(Into::into))457 .as_ref(),458 l.r_brace_token().map(Into::into).as_ref(),459 Some(trailing_for_comp),460 );461 for mem in compspecs {462 if mem.should_start_with_newline {463 p!(out, nl);464 }465 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);466 p!(out, { mem.value });467 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);468 }469 if end_comments.should_start_with_newline {470 p!(out, nl);471 }472 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);473474 p!(out, nl <i str("}"));475 }476 Self::ObjBodyMemberList(l) => {477 let (children, end_comments) = children_between::<Member>(478 l.syntax().clone(),479 l.l_brace_token().map(Into::into).as_ref(),480 l.r_brace_token().map(Into::into).as_ref(),481 None,482 );483 if children.is_empty() && end_comments.is_empty() {484 p!(out, str("{ }"));485 return;486 }487488 let source_is_multiline = children.iter().any(|c| c.triggers_multiline)489 || end_comments.should_start_with_newline;490491 let start = LineNumber::new("obj start line");492 let end = LineNumber::new("obj end line");493 let multi_line: ConditionResolver = if source_is_multiline {494 true_resolver()495 } else {496 Rc::new(move |ctx: &mut ConditionResolverContext| {497 is_multiple_lines(ctx, start, end)498 })499 };500501 fn gen_members(502 children: Vec<Child<Member>>,503 multi_line: ConditionResolver,504 ) -> PrintItems {505 let mut _out = PrintItems::new();506 let out = &mut _out;507 let mut members = children.into_iter().peekable();508 while let Some(mem) = members.next() {509 if mem.should_start_with_newline {510 p!(out, nl);511 }512 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);513 p!(out, { mem.value });514 let has_more = members.peek().is_some();515 if has_more {516 p!(out, str(","));517 } else {518 p!(out, if("trailing comma", multi_line, str(",")));519 }520 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);521 p!(out, if_else("member separator", multi_line, nl)(sonl));522 }523 _out524 }525526 let members_items =527 new_line_group(gen_members(children, multi_line.clone())).into_rc_path();528529 let members = with_indent_eoi(multi_line, members_items.into(), end_comments);530531 p!(out, str("{") info(start));532 p!(out, items(members));533 p!(out, str("}") info(end));534 }535 }536 }537}538impl Printable for UnaryOperator {539 fn print(&self, out: &mut PrintItems) {540 p!(out, string(self.text().to_string()));541 }542}543impl Printable for BinaryOperator {544 fn print(&self, out: &mut PrintItems) {545 p!(out, string(self.text().to_string()));546 }547}548impl Printable for Bind {549 fn print(&self, out: &mut PrintItems) {550 match self {551 Self::BindDestruct(d) => {552 p!(out, {d.into()} str(" = ") {d.value()});553 }554 Self::BindFunction(f) => {555 p!(out, {f.name()} {f.params()} str(" = ") {f.value()});556 }557 }558 }559}560impl Printable for Literal {561 fn print(&self, out: &mut PrintItems) {562 p!(out, string(self.syntax().to_string()));563 }564}565impl Printable for ImportKind {566 fn print(&self, out: &mut PrintItems) {567 p!(out, string(self.syntax().to_string()));568 }569}570impl Printable for ForSpec {571 fn print(&self, out: &mut PrintItems) {572 p!(out, str("for ") {self.bind()} str(" in ") {self.expr()});573 }574}575impl Printable for IfSpec {576 fn print(&self, out: &mut PrintItems) {577 p!(out, str("if ") {self.expr()});578 }579}580impl Printable for CompSpec {581 fn print(&self, out: &mut PrintItems) {582 match self {583 Self::ForSpec(f) => f.print(out),584 Self::IfSpec(i) => i.print(out),585 }586 }587}588impl Printable for Expr {589 fn print(&self, out: &mut PrintItems) {590 let (stmts, _ending) = children_between::<Stmt>(591 self.syntax().clone(),592 None,593 self.expr_base()594 .as_ref()595 .map(ExprBase::syntax)596 .cloned()597 .map(Into::into)598 .as_ref(),599 None,600 );601 for stmt in stmts {602 p!(out, { stmt.value });603 }604 p!(out, { self.expr_base() });605 let (suffixes, _ending) = children_between::<Suffix>(606 self.syntax().clone(),607 self.expr_base()608 .as_ref()609 .map(ExprBase::syntax)610 .cloned()611 .map(Into::into)612 .as_ref(),613 None,614 None,615 );616 for suffix in suffixes {617 p!(out, { suffix.value });618 }619 }620}621impl Printable for Suffix {622 fn print(&self, out: &mut PrintItems) {623 match self {624 Self::SuffixIndex(i) => {625 if i.question_mark_token().is_some() {626 p!(out, str("?"));627 }628 p!(out, str(".") {i.index()});629 }630 Self::SuffixIndexExpr(e) => {631 if e.question_mark_token().is_some() {632 p!(out, str(".?"));633 }634 p!(out, str("[") {e.index()} str("]"));635 }636 Self::SuffixSlice(d) => {637 p!(out, { d.slice_desc() });638 }639 Self::SuffixApply(a) => {640 p!(out, { a.args_desc() });641 }642 }643 }644}645impl Printable for Stmt {646 fn print(&self, out: &mut PrintItems) {647 match self {648 Self::StmtLocal(l) => {649 let (binds, end_comments) = children_between::<Bind>(650 l.syntax().clone(),651 l.local_kw_token().map(Into::into).as_ref(),652 l.semi_token().map(Into::into).as_ref(),653 None,654 );655 if binds.len() == 1 {656 let bind = &binds[0];657 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);658 p!(out, str("local ") {bind.value});659 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?660 } else {661 p!(out,str("local") >i nl);662 for bind in binds {663 if bind.should_start_with_newline {664 p!(out, nl);665 }666 format_comments(&bind.before_trivia, CommentLocation::AboveItem, out);667 p!(out, {bind.value} str(","));668 format_comments(&bind.inline_trivia, CommentLocation::ItemInline, out);669 p!(out, nl);670 }671 if end_comments.should_start_with_newline {672 p!(out, nl);673 }674 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);675 p!(out,<i);676 }677 p!(out,str(";") nl);678 }679 Self::StmtAssert(a) => {680 p!(out, {a.assertion()} str(";") nl);681 }682 }683 }684}685impl Printable for ExprBase {686 fn print(&self, out: &mut PrintItems) {687 match self {688 Self::ExprBinary(b) => {689 p!(out, {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()});690 }691 Self::ExprUnary(u) => p!(out, {u.unary_operator()} {u.rhs()}),692 // Self::ExprSlice(s) => {693 // p!(new: {s.expr()} {s.slice_desc()})694 // }695 // Self::ExprIndex(i) => {696 // p!(new: {i.expr()} str(".") {i.index()})697 // }698 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),699 // Self::ExprApply(a) => {700 // let mut pi = p!(new: {a.expr()} {a.args_desc()});701 // if a.tailstrict_kw_token().is_some() {702 // p!(out,str(" tailstrict"));703 // }704 // pi705 // }706 Self::ExprObjExtend(ex) => {707 p!(out, {ex.lhs_work()} str(" ") {ex.rhs_work()});708 }709 Self::ExprParened(p) => {710 p!(out, str("(") {p.expr()} str(")"));711 }712 Self::ExprString(s) => p!(out, { s.text() }),713 Self::ExprNumber(n) => p!(out, { n.number() }),714 Self::ExprArray(a) => {715 p!(out, str("[") >i nl);716 for el in a.exprs() {717 p!(out, {el} str(",") nl);718 }719 p!(out, <i str("]"));720 }721 Self::ExprObject(obj) => {722 p!(out, { obj.obj_body() });723 }724 Self::ExprArrayComp(arr) => {725 p!(out, str("[") {arr.expr()});726 for spec in arr.comp_specs() {727 p!(out, str(" ") {spec});728 }729 p!(out, str("]"));730 }731 Self::ExprImport(v) => {732 p!(out, {v.import_kind()} str(" ") {v.text()});733 }734 Self::ExprVar(n) => p!(out, { n.name() }),735 // Self::ExprLocal(l) => {736 // }737 Self::ExprIfThenElse(ite) => {738 p!(out, str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});739 if ite.else_kw_token().is_some() || ite.else_().is_some() {740 p!(out, str(" else ") {ite.else_().map(|t| t.expr())});741 }742 }743 Self::ExprFunction(f) => p!(out, str("function") {f.params_desc()} nl {f.expr()}),744 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),745 Self::ExprError(e) => p!(out, str("error ") {e.expr()}),746 Self::ExprLiteral(l) => {747 p!(out, { l.literal() });748 }749 }750 }751}752753impl Printable for SourceFile {754 fn print(&self, out: &mut PrintItems) {755 let before = trivia_before(756 self.syntax().clone(),757 self.expr()758 .map(|e| e.syntax().clone())759 .map(Into::into)760 .as_ref(),761 );762 let after = trivia_after(763 self.syntax().clone(),764 self.expr()765 .map(|e| e.syntax().clone())766 .map(Into::into)767 .as_ref(),768 );769 format_comments(&before, CommentLocation::AboveItem, out);770 p!(out, {self.expr()} nl);771 format_comments(&after, CommentLocation::EndOfItems, out);772 }773}774775pub struct FormatOptions {776 // 0 for hard tabs777 pub indent: u8,778}779pub fn format(input: &str, opts: &FormatOptions) -> Result<String, SnippetBuilder> {780 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);781 if !errors.is_empty() {782 let mut builder = hi_doc::SnippetBuilder::new(input);783 for error in errors {784 builder785 .error(hi_doc::Text::fragment(786 format!("{:?}", error.error),787 Formatting::default(),788 ))789 .range(790 error.range.start().into()791 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),792 )793 .build();794 }795 // let snippet = builder.build();796 return Err(builder);797 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip798 // ERROR rowan nodes.799 // Recovery needs to be enabled for LSP, though.800 }801 Ok(dprint_core::formatting::format(802 || {803 let mut out = PrintItems::new();804 parsed.print(&mut out);805 out806 },807 PrintOptions {808 indent_width: if opts.indent == 0 {809 // Reasonable max length for both 2 and 4 space sized tabs.810 3811 } else {812 opts.indent813 },814 max_width: 100,815 use_tabs: opts.indent == 0,816 new_line_text: "\n",817 },818 ))819}crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snapdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snap
@@ -1,6 +1,6 @@
---
source: crates/jrsonnet-formatter/src/tests.rs
-expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n }\"))"
+expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n\n\t\t\t smallObjectWithEnding: {/*Ending comment*/},\n\t\t\t smallObjectWithFieldAndEnding: {a: 11/*Ending comment*/},\n\t\t\t smallObjectWithFieldAndEnding2: {/*Start*/a: 11/*Ending comment*/},\n }\"))"
---
{
comments: {
@@ -50,4 +50,13 @@
a: '',
b: '',
},
+
+ smallObjectWithEnding: {
+ /* Ending comment */
+ },
+ smallObjectWithFieldAndEnding: { a: 11 /* Ending comment */ },
+ smallObjectWithFieldAndEnding2: {
+ /* Start */
+ a: 11, /* Ending comment */
+ },
}
crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_nested.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_nested.snap
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-formatter/src/tests.rs
+expression: "reformat(indoc!(\"\n\t\t\t{\n\t\t\t\tkubernetes: {\n\t\t\t\t deployment: {\n\t\t\t\t\tapiVersion: 'apps/v1',\n\t\t\t\t\tkind: 'Deployment',\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t name: 'myapp',\n\t\t\t\t\t labels: { app: 'myapp', version: 'v1' },\n\t\t\t\t\t},\n\t\t\t\t\tspec: {\n\t\t\t\t\t replicas: 3,\n\t\t\t\t\t selector: { matchLabels: { app: 'myapp' } },\n\t\t\t\t\t template: {\n\t\t\t\t\t\t metadata: { labels: { app: 'myapp' } },\n\t\t\t\t\t\t spec: {\n\t\t\t\t\t\t\tcontainers: [\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t name: 'myapp',\n\t\t\t\t\t\t\t\t image: 'myapp:latest',\n\t\t\t\t\t\t\t\t ports: [{ containerPort: 8080 }],\n\t\t\t\t\t\t\t\t env: [\n\t\t\t\t\t\t\t\t\t{ name: 'FOO', value: 'bar' },\n\t\t\t\t\t\t\t\t\t{ name: 'BAZ', valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } } },\n\t\t\t\t\t\t\t\t ],\n\t\t\t\t\t\t\t },\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t },\n\t\t\t\t\t },\n\t\t\t\t\t},\n\t\t\t\t },\n\t\t\t },\n\t\t\t}\n\t\t\"))"
+---
+{
+ kubernetes: {
+ deployment: {
+ apiVersion: 'apps/v1',
+ kind: 'Deployment',
+ metadata: {
+ name: 'myapp',
+ labels: { app: 'myapp', version: 'v1' },
+ },
+ spec: {
+ replicas: 3,
+ selector: { matchLabels: { app: 'myapp' } },
+ template: {
+ metadata: { labels: { app: 'myapp' } },
+ spec: {
+ containers: [
+ {
+ name: 'myapp',
+ image: 'myapp:latest',
+ ports: [
+ { containerPort: 8080 },
+ ],
+ env: [
+ { name: 'FOO', value: 'bar' },
+ {
+ name: 'BAZ',
+ valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+}
crates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/tests.rs
+++ b/crates/jrsonnet-formatter/src/tests.rs
@@ -74,6 +74,10 @@
a: '',
b: '',
},
+
+ smallObjectWithEnding: {/*Ending comment*/},
+ smallObjectWithFieldAndEnding: {a: 11/*Ending comment*/},
+ smallObjectWithFieldAndEnding2: {/*Start*/a: 11/*Ending comment*/},
}"
)));
}
@@ -104,3 +108,43 @@
"
)));
}
+
+#[test]
+fn complex_nested() {
+ insta::assert_snapshot!(reformat(indoc!(
+ "
+ {
+ kubernetes: {
+ deployment: {
+ apiVersion: 'apps/v1',
+ kind: 'Deployment',
+ metadata: {
+ name: 'myapp',
+ labels: { app: 'myapp', version: 'v1' },
+ },
+ spec: {
+ replicas: 3,
+ selector: { matchLabels: { app: 'myapp' } },
+ template: {
+ metadata: { labels: { app: 'myapp' } },
+ spec: {
+ containers: [
+ {
+ name: 'myapp',
+ image: 'myapp:latest',
+ ports: [{ containerPort: 8080 }],
+ env: [
+ { name: 'FOO', value: 'bar' },
+ { name: 'BAZ', valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } } },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ }
+ "
+ )));
+}