difftreelog
feat(fmt) preserve comments in locals
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
@@ -40,10 +40,9 @@
}
let mut iter = node.children_with_tokens().peekable();
while iter.peek() != start {
- // println!("Skipped {}");
- dbg!(&iter.next());
+ iter.next();
}
- dbg!(&iter.next());
+ iter.next();
let mut out = Vec::new();
for item in iter {
if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
@@ -59,6 +58,36 @@
out
}
+pub fn trivia_between(
+ node: SyntaxNode,
+ start: Option<&SyntaxElement>,
+ end: Option<&SyntaxElement>,
+) -> ChildTrivia {
+ let mut iter = node.children_with_tokens().peekable();
+ while iter.peek() != start {
+ iter.next();
+ }
+ iter.next();
+
+ let loose = start.is_none() || end.is_none();
+
+ let mut out = Vec::new();
+ for item in iter.take_while(|i| Some(i) != end) {
+ if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {
+ out.push(trivia);
+ } else if loose {
+ break;
+ } else {
+ assert!(
+ TS![, ;].contains(item.kind()) || item.kind() == ERROR,
+ "silently eaten token: {:?}",
+ item.kind()
+ )
+ }
+ }
+ out
+}
+
pub fn children_between<T: AstNode + Debug>(
node: SyntaxNode,
start: Option<&SyntaxElement>,
cmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/comments.rs
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -12,6 +12,7 @@
EndOfItems,
}
+#[must_use]
pub fn format_comments(comments: &ChildTrivia, loc: CommentLocation) -> PrintItems {
let mut pi = p!(new:);
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},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: str("local") >i nl);440 for bind in l.binds() {441 p!(pi: {bind} str(",") nl);442 }443 p!(pi: <i str(";") nl {l.expr()});444 pi445 }446 Expr::ExprIfThenElse(ite) => {447 let mut pi =448 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});449 if ite.else_kw_token().is_some() || ite.else_().is_some() {450 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})451 }452 pi453 }454 Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),455 Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),456 Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),457 Expr::ExprLiteral(l) => {458 p!(new: {l.literal()})459 }460 }461 }462}463464impl Printable for SourceFile {465 fn print(&self) -> PrintItems {466 let mut pi = p!(new:);467 let before = trivia_before(468 self.syntax().clone(),469 self.expr()470 .map(|e| e.syntax().clone())471 .map(Into::into)472 .as_ref(),473 );474 let after = trivia_after(475 self.syntax().clone(),476 self.expr()477 .map(|e| e.syntax().clone())478 .map(Into::into)479 .as_ref(),480 );481 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));482 p!(pi: {self.expr()} nl);483 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));484 pi485 }486}487488fn main() {489 let (parsed, _errors) = jrsonnet_rowan_parser::parse(490 r#"491492493 # Edit me!494 local b = import "b.libsonnet"; # comment495 local a = import "a.libsonnet";496497 local f(x,y)=x+y;498499 local {a: [b, ..., c], d, ...e} = null;500501 local ass = assert false : false; false;502503 local fn = function(a, b, c = 3) 4;504505 local comp = [a for b in c if d == e];506 local ocomp = {[k]: 1 for k in v};507508 local ? = skip;509510 local intr = $intrinsic(test);511 local intrId = $intrinsicId;512 local intrThisFile = $intrinsicThisFile;513514 local ie = a[expr];515516 local unary = !a;517518 local Template = {z: "foo"};519520 {521 local522523 h = 3,524 assert self.a == 1525526 : "error",527 "f": ((((((3)))))) ,528 "g g":529 f(4,2),530 arr: [[531 1, 2,532 ],533 3,534 {535 b: {536 c: {537 k: [16]538 }539 }540 }541 ],542 m: a[1::],543 m: b[::],544545 comments: {546 _: '',547 // Plain comment548 a: '',549550 # Plain comment with empty line before551 b: '',552 /*Single-line multiline comment553554 */555 c: '',556557 /**Single-line multiline doc comment558559 */560 c: '',561562 /**multiline doc comment563 s564 */565 c: '',566567 /*568569 Multi-line570571 comment572 */573 d: '',574575 e: '', // Inline comment576577 k: '',578579 // Text after everything580 },581 comments2: {582 k: '',583 // Text after everything, but no newline above584 },585 k: if a == b then586587588 2589590 else Template {}591 } + Template592593594 // Comment after everything595"#,596 );597598 // dbg!(errors);599 dbg!(&parsed);600601 let o = dprint_core::formatting::format(602 || parsed.print(),603 PrintOptions {604 indent_width: 2,605 max_width: 100,606 use_tabs: false,607 new_line_text: "\n",608 },609 );610 println!("{}", o);611}