difftreelog
feat(fmt) preserve newline above last comment
in: master
3 files changed
cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/children.rs
+++ b/cmds/jrsonnet-fmt/src/children.rs
@@ -1,6 +1,6 @@
// TODO: Return errors as trivia
-use std::{fmt::Debug, marker::PhantomData, mem};
+use std::{fmt::Debug, mem};
use jrsonnet_rowan_parser::{
nodes::{Trivia, TriviaKind},
@@ -62,7 +62,7 @@
node: SyntaxNode,
start: Option<&SyntaxElement>,
end: Option<&SyntaxElement>,
-) -> ChildTrivia {
+) -> EndingComments {
let mut iter = node.children_with_tokens().peekable();
while iter.peek() != start {
iter.next();
@@ -85,14 +85,17 @@
)
}
}
- out
+ EndingComments {
+ should_start_with_newline: should_start_with_newline(None, &out),
+ trivia: out,
+ }
}
pub fn children_between<T: AstNode + Debug>(
node: SyntaxNode,
start: Option<&SyntaxElement>,
end: Option<&SyntaxElement>,
-) -> (Vec<Child<T>>, ChildTrivia) {
+) -> (Vec<Child<T>>, EndingComments) {
let mut iter = node.children_with_tokens().peekable();
while iter.peek() != start {
iter.next();
@@ -104,9 +107,14 @@
)
}
-pub fn should_start_with_newline(tt: &ChildTrivia) -> bool {
- // First for previous item end
- count_newlines_before(tt) >= 2
+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()
+
+ // First for previous item end, second for current item
+ >= 2
}
fn count_newlines_before(tt: &ChildTrivia) -> usize {
@@ -142,10 +150,10 @@
nl_count
}
-pub fn children<'a, T: AstNode + Debug>(
+pub fn children<T: AstNode + Debug>(
items: impl Iterator<Item = SyntaxElement>,
loose: bool,
-) -> (Vec<Child<T>>, ChildTrivia) {
+) -> (Vec<Child<T>>, EndingComments) {
let mut out = Vec::new();
let mut current_child = None::<Child<T>>;
let mut next = ChildTrivia::new();
@@ -157,15 +165,12 @@
if let Some(value) = item.as_node().cloned().and_then(T::cast) {
let before_trivia = mem::take(&mut next);
let last_child = current_child.replace(Child {
- newlines_above: if had_some {
- count_newlines_before(&before_trivia)
- + current_child
- .as_ref()
- .map(|c| count_newlines_after(&c.inline_trivia))
- .unwrap_or_default()
- } else {
- 0
- },
+ // 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,
+ ),
before_trivia,
value,
inline_trivia: Vec::new(),
@@ -206,16 +211,25 @@
}
}
+ let ending_comments = EndingComments {
+ should_start_with_newline: should_start_with_newline(
+ current_child.as_ref().map(|c| &c.inline_trivia),
+ &next,
+ ),
+ trivia: next,
+ };
+
if let Some(current_child) = current_child {
out.push(current_child);
}
- (out, next)
+ (out, ending_comments)
}
#[derive(Debug)]
pub struct Child<T> {
- newlines_above: usize,
+ /// If this child has two newlines above in source code, so it needs to have it in the output
+ pub should_start_with_newline: bool,
/// Comment before item, i.e
///
/// ```ignore
@@ -234,10 +248,8 @@
pub inline_trivia: ChildTrivia,
}
-impl<T> Child<T> {
- /// If this child has two newlines above in source code, so it needs to have it in output
- pub fn needs_newline_above(&self) -> bool {
- // First line for end of previous item
- self.newlines_above >= 2
- }
+pub struct EndingComments {
+ /// If this child has two newlines above in source code, so it needs to have it in the output
+ pub should_start_with_newline: bool,
+ pub trivia: ChildTrivia,
}
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth1use std::any::type_name;23use children::{children_between, trivia_before};4use dprint_core::formatting::{PrintItems, PrintOptions};5use jrsonnet_rowan_parser::{6 nodes::{7 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,8 DestructRest, Expr, Field, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal,9 Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text,10 UnaryOperator,11 },12 AstNode, AstToken, SyntaxToken,13};1415use crate::{16 children::{should_start_with_newline, trivia_after, trivia_between},17 comments::{format_comments, CommentLocation},18};1920mod children;21mod comments;22#[cfg(test)]23mod tests;2425pub trait Printable {26 fn print(&self) -> PrintItems;27}2829macro_rules! pi {30 (@i; $($t:tt)*) => {{31 #[allow(unused_mut)]32 let mut o = dprint_core::formatting::PrintItems::new();33 pi!(@s; o: $($t)*);34 o35 }};36 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{37 $o.push_str($e);38 pi!(@s; $o: $($t)*);39 }};40 (@s; $o:ident: nl $($t:tt)*) => {{41 $o.push_signal(dprint_core::formatting::Signal::NewLine);42 pi!(@s; $o: $($t)*);43 }};44 (@s; $o:ident: tab $($t:tt)*) => {{45 $o.push_signal(dprint_core::formatting::Signal::Tab);46 pi!(@s; $o: $($t)*);47 }};48 (@s; $o:ident: >i $($t:tt)*) => {{49 $o.push_signal(dprint_core::formatting::Signal::StartIndent);50 pi!(@s; $o: $($t)*);51 }};52 (@s; $o:ident: <i $($t:tt)*) => {{53 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);54 pi!(@s; $o: $($t)*);55 }};56 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{57 $o.extend($expr.print());58 pi!(@s; $o: $($t)*);59 }};60 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{61 $o.extend($expr);62 pi!(@s; $o: $($t)*);63 }};64 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{65 if $e {66 pi!(@s; $o: $($then)*);67 }68 pi!(@s; $o: $($t)*);69 }};70 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{71 if $e {72 pi!(@s; $o: $($then)*);73 } else {74 pi!(@s; $o: $($else)*);75 }76 pi!(@s; $o: $($t)*);77 }};78 (@s; $i:ident:) => {}79}80macro_rules! p {81 (new: $($t:tt)*) => {82 pi!(@i; $($t)*)83 };84 ($o:ident: $($t:tt)*) => {85 pi!(@s; $o: $($t)*)86 };87}88pub(crate) use p;89pub(crate) use pi;9091impl<P> Printable for Option<P>92where93 P: Printable,94{95 fn print(&self) -> PrintItems {96 if let Some(v) = self {97 v.print()98 } else {99 p!(new: str(100 &format!(101 "/*missing {}*/",102 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")103 ),104 ))105 }106 }107}108109impl Printable for SyntaxToken {110 fn print(&self) -> PrintItems {111 p!(new: str(&self.to_string()))112 }113}114115impl Printable for Text {116 fn print(&self) -> PrintItems {117 p!(new: str(&format!("{}", self)))118 }119}120impl Printable for Number {121 fn print(&self) -> PrintItems {122 p!(new: str(&format!("{}", self)))123 }124}125126impl Printable for Name {127 fn print(&self) -> PrintItems {128 p!(new: {self.ident_lit()})129 }130}131132impl Printable for DestructRest {133 fn print(&self) -> PrintItems {134 let mut pi = p!(new: str("..."));135 if let Some(name) = self.into() {136 p!(pi: {name});137 }138 pi139 }140}141142impl Printable for Destruct {143 fn print(&self) -> PrintItems {144 let mut pi = p!(new:);145 match self {146 Destruct::DestructFull(f) => {147 p!(pi: {f.name()})148 }149 Destruct::DestructSkip(_) => p!(pi: str("?")),150 Destruct::DestructArray(a) => {151 p!(pi: str("[") >i nl);152 for el in a.destruct_array_parts() {153 match el {154 DestructArrayPart::DestructArrayElement(e) => {155 p!(pi: {e.destruct()} str(",") nl)156 }157 DestructArrayPart::DestructRest(d) => {158 p!(pi: {d} str(",") nl)159 }160 }161 }162 p!(pi: <i str("]"));163 }164 Destruct::DestructObject(o) => {165 p!(pi: str("{") >i nl);166 for item in o.destruct_object_fields() {167 p!(pi: {item.field()});168 if let Some(des) = item.destruct() {169 p!(pi: str(": ") {des})170 }171 if let Some(def) = item.expr() {172 p!(pi: str(" = ") {def});173 }174 p!(pi: str(",") nl);175 }176 if let Some(rest) = o.destruct_rest() {177 p!(pi: {rest} nl)178 }179 p!(pi: <i str("}"));180 }181 }182 pi183 }184}185186impl Printable for FieldName {187 fn print(&self) -> PrintItems {188 match self {189 FieldName::FieldNameFixed(f) => {190 if let Some(id) = f.id() {191 p!(new: {id})192 } else if let Some(str) = f.text() {193 p!(new: {str})194 } else {195 p!(new: str("/*missing FieldName*/"))196 }197 }198 FieldName::FieldNameDynamic(d) => {199 p!(new: str("[") {d.expr()} str("]"))200 }201 }202 }203}204impl Printable for Field {205 fn print(&self) -> PrintItems {206 let mut pi = p!(new:);207 match self {208 Field::FieldNormal(n) => {209 p!(pi: {n.field_name()});210 if n.plus_token().is_some() {211 p!(pi: str("+"));212 }213 p!(pi: str(": ") {n.expr()});214 }215 Field::FieldMethod(m) => {216 p!(pi: {m.field_name()} {m.params_desc()} str(": ") {m.expr()});217 }218 }219 pi220 }221}222223impl Printable for ObjLocal {224 fn print(&self) -> PrintItems {225 p!(new: str("local ") {self.bind()})226 }227}228229impl Printable for Assertion {230 fn print(&self) -> PrintItems {231 let mut pi = p!(new: str("assert ") {self.condition()});232 if self.colon_token().is_some() || self.message().is_some() {233 p!(pi: str(": ") {self.message()})234 }235 pi236 }237}238239impl Printable for ParamsDesc {240 fn print(&self) -> PrintItems {241 let mut pi = p!(new: str("(") >i nl);242 for param in self.params() {243 p!(pi: {param.destruct()});244 if param.assign_token().is_some() || param.expr().is_some() {245 p!(pi: str(" = ") {param.expr()})246 }247 p!(pi: str(",") nl)248 }249 p!(pi: <i str(")"));250 pi251 }252}253impl Printable for ArgsDesc {254 fn print(&self) -> PrintItems {255 let mut pi = p!(new: str("(") >i nl);256 for arg in self.args() {257 if arg.name().is_some() || arg.assign_token().is_some() {258 p!(pi: {arg.name()} str(" = "));259 }260 p!(pi: {arg.expr()} str(",") nl)261 }262 p!(pi: <i str(")"));263 pi264 }265}266impl Printable for SliceDesc {267 fn print(&self) -> PrintItems {268 let mut pi = p!(new: str("["));269 if self.from().is_some() {270 p!(pi: {self.from()});271 }272 p!(pi: str(":"));273 if self.end().is_some() {274 p!(pi: {self.end().map(|e|e.expr())})275 }276 // Keep only one : in case if we don't need step277 if self.step().is_some() {278 p!(pi: str(":") {self.step().map(|e|e.expr())});279 }280 p!(pi: str("]"));281 pi282 }283}284285impl Printable for ObjBody {286 fn print(&self) -> PrintItems {287 match self {288 ObjBody::ObjBodyComp(_) => todo!(),289 ObjBody::ObjBodyMemberList(l) => {290 let mut pi = p!(new: str("{") >i nl);291 let (children, end_comments) = children_between::<Member>(292 l.syntax().clone(),293 l.l_brace_token().map(Into::into).as_ref(),294 l.r_brace_token().map(Into::into).as_ref(),295 );296 for mem in children.into_iter() {297 if mem.needs_newline_above() {298 p!(pi: nl);299 }300 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));301 match mem.value {302 Member::MemberBindStmt(b) => {303 p!(pi: {b.obj_local()})304 }305 Member::MemberAssertStmt(ass) => {306 p!(pi: {ass.assertion()})307 }308 Member::MemberField(f) => {309 p!(pi: {f.field()})310 }311 }312 p!(pi: str(","));313 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));314 p!(pi: nl)315 }316317 // TODO: implement same thing as needs_newline_above, but for end comments318 if should_start_with_newline(&end_comments) {319 p!(pi: nl);320 }321 p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));322 p!(pi: <i str("}"));323 pi324 }325 }326 }327}328impl Printable for UnaryOperator {329 fn print(&self) -> PrintItems {330 p!(new: str(self.text()))331 }332}333impl Printable for BinaryOperator {334 fn print(&self) -> PrintItems {335 p!(new: str(self.text()))336 }337}338impl Printable for Bind {339 fn print(&self) -> PrintItems {340 match self {341 Bind::BindDestruct(d) => {342 p!(new: {d.into()} str(" = ") {d.value()})343 }344 Bind::BindFunction(f) => {345 p!(new: str("function") {f.params()} str(" = ") {f.value()})346 }347 }348 }349}350impl Printable for Literal {351 fn print(&self) -> PrintItems {352 p!(new: str(&self.syntax().to_string()))353 }354}355impl Printable for ImportKind {356 fn print(&self) -> PrintItems {357 p!(new: str(&self.syntax().to_string()))358 }359}360impl Printable for LhsExpr {361 fn print(&self) -> PrintItems {362 p!(new: {self.expr()})363 }364}365impl Printable for ForSpec {366 fn print(&self) -> PrintItems {367 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})368 }369}370impl Printable for IfSpec {371 fn print(&self) -> PrintItems {372 p!(new: str("if ") {self.expr()})373 }374}375impl Printable for CompSpec {376 fn print(&self) -> PrintItems {377 match self {378 CompSpec::ForSpec(f) => f.print(),379 CompSpec::IfSpec(i) => i.print(),380 }381 }382}383impl Printable for Expr {384 fn print(&self) -> PrintItems {385 match self {386 Expr::ExprBinary(b) => {387 p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})388 }389 Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),390 Expr::ExprSlice(s) => {391 p!(new: {s.expr()} {s.slice_desc()})392 }393 Expr::ExprIndex(i) => {394 p!(new: {i.expr()} str(".") {i.index()})395 }396 Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),397 Expr::ExprApply(a) => {398 let mut pi = p!(new: {a.expr()} {a.args_desc()});399 if a.tailstrict_kw_token().is_some() {400 p!(pi: str(" tailstrict"));401 }402 pi403 }404 Expr::ExprObjExtend(ex) => {405 p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})406 }407 Expr::ExprParened(p) => {408 p!(new: str("(") {p.expr()} str(")"))409 }410 Expr::ExprIntrinsicThisFile(_) => p!(new: str("$intrinsicThisFile")),411 Expr::ExprIntrinsicId(_) => p!(new: str("$intrinsicId")),412 Expr::ExprIntrinsic(i) => p!(new: str("$intrinsic(") {i.name()} str(")")),413 Expr::ExprString(s) => p!(new: {s.text()}),414 Expr::ExprNumber(n) => p!(new: {n.number()}),415 Expr::ExprArray(a) => {416 let mut pi = p!(new: str("[") >i nl);417 for el in a.exprs() {418 p!(pi: {el} str(",") nl);419 }420 p!(pi: <i str("]"));421 pi422 }423 Expr::ExprObject(o) => {424 p!(new: {o.obj_body()})425 }426 Expr::ExprArrayComp(arr) => {427 let mut pi = p!(new: str("[") {arr.expr()});428 for spec in arr.comp_specs() {429 p!(pi: str(" ") {spec});430 }431 p!(pi: str("]"));432 pi433 }434 Expr::ExprImport(v) => {435 p!(new: {v.import_kind()} str(" ") {v.text()})436 }437 Expr::ExprVar(n) => p!(new: {n.name()}),438 Expr::ExprLocal(l) => {439 let mut pi = p!(new:);440 let (binds, end_comments) = children_between::<Bind>(441 l.syntax().clone(),442 l.local_kw_token().map(Into::into).as_ref(),443 l.semi_token().map(Into::into).as_ref(),444 );445 if binds.len() == 1 {446 let bind = &binds[0];447 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));448 p!(pi: str("local ") {bind.value});449 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?450 } else {451 p!(pi: str("local") >i nl);452 for bind in binds {453 if bind.needs_newline_above() {454 p!(pi: nl);455 }456 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));457 p!(pi: {bind.value} str(";"));458 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);459 }460 // TODO: needs_newline_above end_comments461 p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));462 p!(pi: <i);463 }464 p!(pi: str(";") nl);465466 let expr_comments = trivia_between(467 l.syntax().clone(),468 l.semi_token().map(Into::into).as_ref(),469 l.expr()470 .map(|e| e.syntax().clone())471 .map(Into::into)472 .as_ref(),473 );474 p!(pi: items(format_comments(&expr_comments, CommentLocation::AboveItem)));475476 // TODO: needs_newline_above expr477 p!(pi: {l.expr()});478 pi479 }480 Expr::ExprIfThenElse(ite) => {481 let mut pi =482 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});483 if ite.else_kw_token().is_some() || ite.else_().is_some() {484 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})485 }486 pi487 }488 Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),489 Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),490 Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),491 Expr::ExprLiteral(l) => {492 p!(new: {l.literal()})493 }494 }495 }496}497498impl Printable for SourceFile {499 fn print(&self) -> PrintItems {500 let mut pi = p!(new:);501 let before = trivia_before(502 self.syntax().clone(),503 self.expr()504 .map(|e| e.syntax().clone())505 .map(Into::into)506 .as_ref(),507 );508 let after = trivia_after(509 self.syntax().clone(),510 self.expr()511 .map(|e| e.syntax().clone())512 .map(Into::into)513 .as_ref(),514 );515 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));516 p!(pi: {self.expr()} nl);517 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));518 pi519 }520}521522fn main() {523 let (parsed, _errors) = jrsonnet_rowan_parser::parse(524 r#"525526527 # Edit me!528 local b = import "b.libsonnet"; # comment529 local a = import "a.libsonnet";530531 local f(x,y)=x+y;532533 local {a: [b, ..., c], d, ...e} = null;534535 local ass = assert false : false; false;536537 local fn = function(a, b, c = 3) 4;538539 local comp = [a for b in c if d == e];540 local ocomp = {[k]: 1 for k in v};541542 local ? = skip;543544 local intr = $intrinsic(test);545 local intrId = $intrinsicId;546 local intrThisFile = $intrinsicThisFile;547548 local ie = a[expr];549550 local unary = !a;551552 local553 // I am comment554 singleLocalWithItemComment = 1,555 ;556557 // Comment between local and expression558559 local560 a = 1, // Inline561 // Comment above b562 b = 4,563564 // c needs some space565 c = 5,566567 // Comment after everything568 ;569570571 local Template = {z: "foo"};572573 {574 local575576 h = 3,577 assert self.a == 1578579 : "error",580 "f": ((((((3)))))) ,581 "g g":582 f(4,2),583 arr: [[584 1, 2,585 ],586 3,587 {588 b: {589 c: {590 k: [16]591 }592 }593 }594 ],595 m: a[1::],596 m: b[::],597598 comments: {599 _: '',600 // Plain comment601 a: '',602603 # Plain comment with empty line before604 b: '',605 /*Single-line multiline comment606607 */608 c: '',609610 /**Single-line multiline doc comment611612 */613 c: '',614615 /**multiline doc comment616 s617 */618 c: '',619620 /*621622 Multi-line623624 comment625 */626 d: '',627628 e: '', // Inline comment629630 k: '',631632 // Text after everything633 },634 comments2: {635 k: '',636 // Text after everything, but no newline above637 },638 k: if a == b then639640641 2642643 else Template {}644 } + Template645646647 // Comment after everything648"#,649 );650651 // dbg!(errors);652 dbg!(&parsed);653654 let o = dprint_core::formatting::format(655 || parsed.print(),656 PrintOptions {657 indent_width: 2,658 max_width: 100,659 use_tabs: false,660 new_line_text: "\n",661 },662 );663 println!("{}", o);664}1use std::any::type_name;23use children::{children_between, trivia_before};4use dprint_core::formatting::{PrintItems, PrintOptions};5use jrsonnet_rowan_parser::{6 nodes::{7 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,8 DestructRest, Expr, Field, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal,9 Member, Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text,10 UnaryOperator,11 },12 AstNode, AstToken, SyntaxToken,13};1415use crate::{16 children::{trivia_after, trivia_between},17 comments::{format_comments, CommentLocation},18};1920mod children;21mod comments;22#[cfg(test)]23mod tests;2425pub trait Printable {26 fn print(&self) -> PrintItems;27}2829macro_rules! pi {30 (@i; $($t:tt)*) => {{31 #[allow(unused_mut)]32 let mut o = dprint_core::formatting::PrintItems::new();33 pi!(@s; o: $($t)*);34 o35 }};36 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{37 $o.push_str($e);38 pi!(@s; $o: $($t)*);39 }};40 (@s; $o:ident: nl $($t:tt)*) => {{41 $o.push_signal(dprint_core::formatting::Signal::NewLine);42 pi!(@s; $o: $($t)*);43 }};44 (@s; $o:ident: tab $($t:tt)*) => {{45 $o.push_signal(dprint_core::formatting::Signal::Tab);46 pi!(@s; $o: $($t)*);47 }};48 (@s; $o:ident: >i $($t:tt)*) => {{49 $o.push_signal(dprint_core::formatting::Signal::StartIndent);50 pi!(@s; $o: $($t)*);51 }};52 (@s; $o:ident: <i $($t:tt)*) => {{53 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);54 pi!(@s; $o: $($t)*);55 }};56 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{57 $o.extend($expr.print());58 pi!(@s; $o: $($t)*);59 }};60 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{61 $o.extend($expr);62 pi!(@s; $o: $($t)*);63 }};64 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{65 if $e {66 pi!(@s; $o: $($then)*);67 }68 pi!(@s; $o: $($t)*);69 }};70 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{71 if $e {72 pi!(@s; $o: $($then)*);73 } else {74 pi!(@s; $o: $($else)*);75 }76 pi!(@s; $o: $($t)*);77 }};78 (@s; $i:ident:) => {}79}80macro_rules! p {81 (new: $($t:tt)*) => {82 pi!(@i; $($t)*)83 };84 ($o:ident: $($t:tt)*) => {85 pi!(@s; $o: $($t)*)86 };87}88pub(crate) use p;89pub(crate) use pi;9091impl<P> Printable for Option<P>92where93 P: Printable,94{95 fn print(&self) -> PrintItems {96 if let Some(v) = self {97 v.print()98 } else {99 p!(new: str(100 &format!(101 "/*missing {}*/",102 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")103 ),104 ))105 }106 }107}108109impl Printable for SyntaxToken {110 fn print(&self) -> PrintItems {111 p!(new: str(&self.to_string()))112 }113}114115impl Printable for Text {116 fn print(&self) -> PrintItems {117 p!(new: str(&format!("{}", self)))118 }119}120impl Printable for Number {121 fn print(&self) -> PrintItems {122 p!(new: str(&format!("{}", self)))123 }124}125126impl Printable for Name {127 fn print(&self) -> PrintItems {128 p!(new: {self.ident_lit()})129 }130}131132impl Printable for DestructRest {133 fn print(&self) -> PrintItems {134 let mut pi = p!(new: str("..."));135 if let Some(name) = self.into() {136 p!(pi: {name});137 }138 pi139 }140}141142impl Printable for Destruct {143 fn print(&self) -> PrintItems {144 let mut pi = p!(new:);145 match self {146 Destruct::DestructFull(f) => {147 p!(pi: {f.name()})148 }149 Destruct::DestructSkip(_) => p!(pi: str("?")),150 Destruct::DestructArray(a) => {151 p!(pi: str("[") >i nl);152 for el in a.destruct_array_parts() {153 match el {154 DestructArrayPart::DestructArrayElement(e) => {155 p!(pi: {e.destruct()} str(",") nl)156 }157 DestructArrayPart::DestructRest(d) => {158 p!(pi: {d} str(",") nl)159 }160 }161 }162 p!(pi: <i str("]"));163 }164 Destruct::DestructObject(o) => {165 p!(pi: str("{") >i nl);166 for item in o.destruct_object_fields() {167 p!(pi: {item.field()});168 if let Some(des) = item.destruct() {169 p!(pi: str(": ") {des})170 }171 if let Some(def) = item.expr() {172 p!(pi: str(" = ") {def});173 }174 p!(pi: str(",") nl);175 }176 if let Some(rest) = o.destruct_rest() {177 p!(pi: {rest} nl)178 }179 p!(pi: <i str("}"));180 }181 }182 pi183 }184}185186impl Printable for FieldName {187 fn print(&self) -> PrintItems {188 match self {189 FieldName::FieldNameFixed(f) => {190 if let Some(id) = f.id() {191 p!(new: {id})192 } else if let Some(str) = f.text() {193 p!(new: {str})194 } else {195 p!(new: str("/*missing FieldName*/"))196 }197 }198 FieldName::FieldNameDynamic(d) => {199 p!(new: str("[") {d.expr()} str("]"))200 }201 }202 }203}204impl Printable for Field {205 fn print(&self) -> PrintItems {206 let mut pi = p!(new:);207 match self {208 Field::FieldNormal(n) => {209 p!(pi: {n.field_name()});210 if n.plus_token().is_some() {211 p!(pi: str("+"));212 }213 p!(pi: str(": ") {n.expr()});214 }215 Field::FieldMethod(m) => {216 p!(pi: {m.field_name()} {m.params_desc()} str(": ") {m.expr()});217 }218 }219 pi220 }221}222223impl Printable for ObjLocal {224 fn print(&self) -> PrintItems {225 p!(new: str("local ") {self.bind()})226 }227}228229impl Printable for Assertion {230 fn print(&self) -> PrintItems {231 let mut pi = p!(new: str("assert ") {self.condition()});232 if self.colon_token().is_some() || self.message().is_some() {233 p!(pi: str(": ") {self.message()})234 }235 pi236 }237}238239impl Printable for ParamsDesc {240 fn print(&self) -> PrintItems {241 let mut pi = p!(new: str("(") >i nl);242 for param in self.params() {243 p!(pi: {param.destruct()});244 if param.assign_token().is_some() || param.expr().is_some() {245 p!(pi: str(" = ") {param.expr()})246 }247 p!(pi: str(",") nl)248 }249 p!(pi: <i str(")"));250 pi251 }252}253impl Printable for ArgsDesc {254 fn print(&self) -> PrintItems {255 let mut pi = p!(new: str("(") >i nl);256 for arg in self.args() {257 if arg.name().is_some() || arg.assign_token().is_some() {258 p!(pi: {arg.name()} str(" = "));259 }260 p!(pi: {arg.expr()} str(",") nl)261 }262 p!(pi: <i str(")"));263 pi264 }265}266impl Printable for SliceDesc {267 fn print(&self) -> PrintItems {268 let mut pi = p!(new: str("["));269 if self.from().is_some() {270 p!(pi: {self.from()});271 }272 p!(pi: str(":"));273 if self.end().is_some() {274 p!(pi: {self.end().map(|e|e.expr())})275 }276 // Keep only one : in case if we don't need step277 if self.step().is_some() {278 p!(pi: str(":") {self.step().map(|e|e.expr())});279 }280 p!(pi: str("]"));281 pi282 }283}284285impl Printable for ObjBody {286 fn print(&self) -> PrintItems {287 match self {288 ObjBody::ObjBodyComp(_) => todo!(),289 ObjBody::ObjBodyMemberList(l) => {290 let mut pi = p!(new: str("{") >i nl);291 let (children, end_comments) = children_between::<Member>(292 l.syntax().clone(),293 l.l_brace_token().map(Into::into).as_ref(),294 l.r_brace_token().map(Into::into).as_ref(),295 );296 for mem in children.into_iter() {297 if mem.should_start_with_newline {298 p!(pi: nl);299 }300 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));301 match mem.value {302 Member::MemberBindStmt(b) => {303 p!(pi: {b.obj_local()})304 }305 Member::MemberAssertStmt(ass) => {306 p!(pi: {ass.assertion()})307 }308 Member::MemberField(f) => {309 p!(pi: {f.field()})310 }311 }312 p!(pi: str(","));313 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));314 p!(pi: nl)315 }316317 if end_comments.should_start_with_newline {318 p!(pi: nl);319 }320 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));321 p!(pi: <i str("}"));322 pi323 }324 }325 }326}327impl Printable for UnaryOperator {328 fn print(&self) -> PrintItems {329 p!(new: str(self.text()))330 }331}332impl Printable for BinaryOperator {333 fn print(&self) -> PrintItems {334 p!(new: str(self.text()))335 }336}337impl Printable for Bind {338 fn print(&self) -> PrintItems {339 match self {340 Bind::BindDestruct(d) => {341 p!(new: {d.into()} str(" = ") {d.value()})342 }343 Bind::BindFunction(f) => {344 p!(new: str("function") {f.params()} str(" = ") {f.value()})345 }346 }347 }348}349impl Printable for Literal {350 fn print(&self) -> PrintItems {351 p!(new: str(&self.syntax().to_string()))352 }353}354impl Printable for ImportKind {355 fn print(&self) -> PrintItems {356 p!(new: str(&self.syntax().to_string()))357 }358}359impl Printable for LhsExpr {360 fn print(&self) -> PrintItems {361 p!(new: {self.expr()})362 }363}364impl Printable for ForSpec {365 fn print(&self) -> PrintItems {366 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})367 }368}369impl Printable for IfSpec {370 fn print(&self) -> PrintItems {371 p!(new: str("if ") {self.expr()})372 }373}374impl Printable for CompSpec {375 fn print(&self) -> PrintItems {376 match self {377 CompSpec::ForSpec(f) => f.print(),378 CompSpec::IfSpec(i) => i.print(),379 }380 }381}382impl Printable for Expr {383 fn print(&self) -> PrintItems {384 match self {385 Expr::ExprBinary(b) => {386 p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})387 }388 Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),389 Expr::ExprSlice(s) => {390 p!(new: {s.expr()} {s.slice_desc()})391 }392 Expr::ExprIndex(i) => {393 p!(new: {i.expr()} str(".") {i.index()})394 }395 Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),396 Expr::ExprApply(a) => {397 let mut pi = p!(new: {a.expr()} {a.args_desc()});398 if a.tailstrict_kw_token().is_some() {399 p!(pi: str(" tailstrict"));400 }401 pi402 }403 Expr::ExprObjExtend(ex) => {404 p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})405 }406 Expr::ExprParened(p) => {407 p!(new: str("(") {p.expr()} str(")"))408 }409 Expr::ExprIntrinsicThisFile(_) => p!(new: str("$intrinsicThisFile")),410 Expr::ExprIntrinsicId(_) => p!(new: str("$intrinsicId")),411 Expr::ExprIntrinsic(i) => p!(new: str("$intrinsic(") {i.name()} str(")")),412 Expr::ExprString(s) => p!(new: {s.text()}),413 Expr::ExprNumber(n) => p!(new: {n.number()}),414 Expr::ExprArray(a) => {415 let mut pi = p!(new: str("[") >i nl);416 for el in a.exprs() {417 p!(pi: {el} str(",") nl);418 }419 p!(pi: <i str("]"));420 pi421 }422 Expr::ExprObject(o) => {423 p!(new: {o.obj_body()})424 }425 Expr::ExprArrayComp(arr) => {426 let mut pi = p!(new: str("[") {arr.expr()});427 for spec in arr.comp_specs() {428 p!(pi: str(" ") {spec});429 }430 p!(pi: str("]"));431 pi432 }433 Expr::ExprImport(v) => {434 p!(new: {v.import_kind()} str(" ") {v.text()})435 }436 Expr::ExprVar(n) => p!(new: {n.name()}),437 Expr::ExprLocal(l) => {438 let mut pi = p!(new:);439 let (binds, end_comments) = children_between::<Bind>(440 l.syntax().clone(),441 l.local_kw_token().map(Into::into).as_ref(),442 l.semi_token().map(Into::into).as_ref(),443 );444 if binds.len() == 1 {445 let bind = &binds[0];446 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));447 p!(pi: str("local ") {bind.value});448 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?449 } else {450 p!(pi: str("local") >i nl);451 for bind in binds {452 if bind.should_start_with_newline {453 p!(pi: nl);454 }455 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));456 p!(pi: {bind.value} str(";"));457 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);458 }459 if end_comments.should_start_with_newline {460 p!(pi: nl)461 }462 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));463 p!(pi: <i);464 }465 p!(pi: str(";") nl);466467 let expr_comments = trivia_between(468 l.syntax().clone(),469 l.semi_token().map(Into::into).as_ref(),470 l.expr()471 .map(|e| e.syntax().clone())472 .map(Into::into)473 .as_ref(),474 );475476 if expr_comments.should_start_with_newline {477 p!(pi: nl);478 }479 p!(pi: items(format_comments(&expr_comments.trivia, CommentLocation::AboveItem)));480 p!(pi: {l.expr()});481 pi482 }483 Expr::ExprIfThenElse(ite) => {484 let mut pi =485 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});486 if ite.else_kw_token().is_some() || ite.else_().is_some() {487 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})488 }489 pi490 }491 Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),492 Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),493 Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),494 Expr::ExprLiteral(l) => {495 p!(new: {l.literal()})496 }497 }498 }499}500501impl Printable for SourceFile {502 fn print(&self) -> PrintItems {503 let mut pi = p!(new:);504 let before = trivia_before(505 self.syntax().clone(),506 self.expr()507 .map(|e| e.syntax().clone())508 .map(Into::into)509 .as_ref(),510 );511 let after = trivia_after(512 self.syntax().clone(),513 self.expr()514 .map(|e| e.syntax().clone())515 .map(Into::into)516 .as_ref(),517 );518 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));519 p!(pi: {self.expr()} nl);520 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));521 pi522 }523}524525fn main() {526 let (parsed, _errors) = jrsonnet_rowan_parser::parse(527 r#"528529530 # Edit me!531 local b = import "b.libsonnet"; # comment532 local a = import "a.libsonnet";533534 local f(x,y)=x+y;535536 local {a: [b, ..., c], d, ...e} = null;537538 local ass = assert false : false; false;539540 local fn = function(a, b, c = 3) 4;541542 local comp = [a for b in c if d == e];543 local ocomp = {[k]: 1 for k in v};544545 local ? = skip;546547 local intr = $intrinsic(test);548 local intrId = $intrinsicId;549 local intrThisFile = $intrinsicThisFile;550551 local ie = a[expr];552553 local unary = !a;554555 local556 // I am comment557 singleLocalWithItemComment = 1,558 ;559560 // Comment between local and expression561562 local563 a = 1, // Inline564 // Comment above b565 b = 4,566567 // c needs some space568 c = 5,569570 // Comment after everything571 ;572573574 local Template = {z: "foo"};575576 {577 local578579 h = 3,580 assert self.a == 1581582 : "error",583 "f": ((((((3)))))) ,584 "g g":585 f(4,2),586 arr: [[587 1, 2,588 ],589 3,590 {591 b: {592 c: {593 k: [16]594 }595 }596 }597 ],598 m: a[1::],599 m: b[::],600601 comments: {602 _: '',603 // Plain comment604 a: '',605606 # Plain comment with empty line before607 b: '',608 /*Single-line multiline comment609610 */611 c: '',612613 /**Single-line multiline doc comment614615 */616 c: '',617618 /**multiline doc comment619 s620 */621 c: '',622623 /*624625 Multi-line626627 comment628 */629 d: '',630631 e: '', // Inline comment632633 k: '',634635 // Text after everything636 },637 comments2: {638 k: '',639 // Text after everything, but no newline above640 },641 k: if a == b then642643644 2645646 else Template {}647 } + Template648649650 // Comment after everything651"#,652 );653654 // dbg!(errors);655 dbg!(&parsed);656657 let o = dprint_core::formatting::format(658 || parsed.print(),659 PrintOptions {660 indent_width: 2,661 max_width: 100,662 use_tabs: false,663 new_line_text: "\n",664 },665 );666 println!("{}", o);667}cmds/jrsonnet-fmt/src/tests.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/tests.rs
+++ b/cmds/jrsonnet-fmt/src/tests.rs
@@ -20,7 +20,8 @@
macro_rules! assert_formatted {
($input:literal, $output:literal) => {
let formatted = reformat(indoc!($input));
- let expected = indoc!($output);
+ let mut expected = indoc!($output).to_owned();
+ expected.push('\n');
if formatted != expected {
panic!(
"bad formatting, expected\n```\n{formatted}\n```\nto be equal to\n```\n{expected}\n```",
@@ -49,7 +50,6 @@
);
}
-// Fails
#[test]
fn last_comment_respects_spacing_with_inline_comment_above() {
assert_formatted!(