difftreelog
feat(fmt) preserve comments in locals
in: master
3 files changed
cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth1// TODO: Return errors as trivia23use std::{fmt::Debug, marker::PhantomData, mem};45use jrsonnet_rowan_parser::{6 nodes::{Trivia, TriviaKind},7 AstNode, AstToken, SyntaxElement,8 SyntaxKind::*,9 SyntaxNode, TS,10};1112pub type ChildTrivia = Vec<Trivia>;1314/// Node should have no non-trivia tokens before element15pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {16 let mut out = Vec::new();17 for item in node.children_with_tokens() {18 if Some(&item) == end {19 break;20 }2122 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {23 out.push(trivia);24 } else if end.is_none() {25 break;26 } else {27 assert!(28 TS![, ;].contains(item.kind()) || item.kind() == ERROR,29 "silently eaten token: {:?}",30 item.kind()31 )32 }33 }34 out35}36/// Node should have no non-trivia tokens after element37pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {38 if start.is_none() {39 return Vec::new();40 }41 let mut iter = node.children_with_tokens().peekable();42 while iter.peek() != start {43 iter.next();44 }45 iter.next();46 let mut out = Vec::new();47 for item in iter {48 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {49 out.push(trivia);50 } else {51 assert!(52 TS![, ;].contains(item.kind()) || item.kind() == ERROR,53 "silently eaten token: {:?}",54 item.kind()55 )56 }57 }58 out59}6061pub fn trivia_between(62 node: SyntaxNode,63 start: Option<&SyntaxElement>,64 end: Option<&SyntaxElement>,65) -> ChildTrivia {66 let mut iter = node.children_with_tokens().peekable();67 while iter.peek() != start {68 iter.next();69 }70 iter.next();7172 let loose = start.is_none() || end.is_none();7374 let mut out = Vec::new();75 for item in iter.take_while(|i| Some(i) != end) {76 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {77 out.push(trivia);78 } else if loose {79 break;80 } else {81 assert!(82 TS![, ;].contains(item.kind()) || item.kind() == ERROR,83 "silently eaten token: {:?}",84 item.kind()85 )86 }87 }88 out89}9091pub fn children_between<T: AstNode + Debug>(92 node: SyntaxNode,93 start: Option<&SyntaxElement>,94 end: Option<&SyntaxElement>,95) -> (Vec<Child<T>>, ChildTrivia) {96 let mut iter = node.children_with_tokens().peekable();97 while iter.peek() != start {98 iter.next();99 }100 iter.next();101 children(102 iter.take_while(|i| Some(i) != end),103 start.is_none() || end.is_none(),104 )105}106107pub fn should_start_with_newline(tt: &ChildTrivia) -> bool {108 // First for previous item end109 count_newlines_before(tt) >= 2110}111112fn count_newlines_before(tt: &ChildTrivia) -> usize {113 let mut nl_count = 0;114 for t in tt {115 match t.kind() {116 TriviaKind::Whitespace => {117 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();118 }119 _ => break,120 }121 }122 nl_count123}124fn count_newlines_after(tt: &ChildTrivia) -> usize {125 let mut nl_count = 0;126 for t in tt.iter().rev() {127 match t.kind() {128 TriviaKind::Whitespace => {129 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();130 }131 TriviaKind::SingleLineHashComment => {132 nl_count += 1;133 break;134 }135 TriviaKind::SingleLineSlashComment => {136 nl_count += 1;137 break;138 }139 _ => {}140 }141 }142 nl_count143}144145pub fn children<'a, T: AstNode + Debug>(146 items: impl Iterator<Item = SyntaxElement>,147 loose: bool,148) -> (Vec<Child<T>>, ChildTrivia) {149 let mut out = Vec::new();150 let mut current_child = None::<Child<T>>;151 let mut next = ChildTrivia::new();152 // Previous element ended, do not add more inline comments153 let mut started_next = false;154 let mut had_some = false;155156 for item in items {157 if let Some(value) = item.as_node().cloned().and_then(T::cast) {158 let before_trivia = mem::take(&mut next);159 let last_child = current_child.replace(Child {160 newlines_above: if had_some {161 count_newlines_before(&before_trivia)162 + current_child163 .as_ref()164 .map(|c| count_newlines_after(&c.inline_trivia))165 .unwrap_or_default()166 } else {167 0168 },169 before_trivia,170 value,171 inline_trivia: Vec::new(),172 });173 if let Some(last_child) = last_child {174 out.push(last_child)175 }176 had_some = true;177 started_next = false;178 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {179 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment180 || trivia.kind() == TriviaKind::SingleLineSlashComment;181 if started_next182 || current_child.is_none()183 || trivia.text().contains('\n') && !is_single_line_comment184 {185 next.push(trivia.clone());186 started_next = true;187 } else {188 let cur = current_child.as_mut().expect("checked not none");189 cur.inline_trivia.push(trivia);190 if is_single_line_comment {191 started_next = true;192 }193 }194 had_some = true;195 } else if loose {196 if had_some {197 break;198 }199 started_next = true;200 } else {201 assert!(202 TS![, ;].contains(item.kind()) || item.kind() == ERROR,203 "silently eaten token: {:?}",204 item.kind()205 )206 }207 }208209 if let Some(current_child) = current_child {210 out.push(current_child);211 }212213 (out, next)214}215216#[derive(Debug)]217pub struct Child<T> {218 newlines_above: usize,219 /// Comment before item, i.e220 ///221 /// ```ignore222 /// // Comment223 /// item224 /// ```225 pub before_trivia: ChildTrivia,226 pub value: T,227 /// Comment after line, but located at same line228 ///229 /// ```ignore230 /// item1, // Inline comment231 /// // Not inline comment232 /// item2,233 /// ```234 pub inline_trivia: ChildTrivia,235}236237impl<T> Child<T> {238 /// If this child has two newlines above in source code, so it needs to have it in output239 pub fn needs_newline_above(&self) -> bool {240 // First line for end of previous item241 self.newlines_above >= 2242 }243}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.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -13,7 +13,7 @@
};
use crate::{
- children::{should_start_with_newline, trivia_after},
+ children::{should_start_with_newline, trivia_after, trivia_between},
comments::{format_comments, CommentLocation},
};
@@ -436,11 +436,45 @@
}
Expr::ExprVar(n) => p!(new: {n.name()}),
Expr::ExprLocal(l) => {
- let mut pi = p!(new: str("local") >i nl);
- for bind in l.binds() {
- p!(pi: {bind} str(",") nl);
+ let mut pi = p!(new:);
+ let (binds, end_comments) = children_between::<Bind>(
+ l.syntax().clone(),
+ l.local_kw_token().map(Into::into).as_ref(),
+ l.semi_token().map(Into::into).as_ref(),
+ );
+ if binds.len() == 1 {
+ let bind = &binds[0];
+ p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
+ p!(pi: str("local ") {bind.value});
+ // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?
+ } else {
+ p!(pi: str("local") >i nl);
+ for bind in binds {
+ if bind.needs_newline_above() {
+ p!(pi: nl);
+ }
+ p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
+ p!(pi: {bind.value} str(";"));
+ p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
+ }
+ // TODO: needs_newline_above end_comments
+ p!(pi: items(format_comments(&end_comments, CommentLocation::EndOfItems)));
+ p!(pi: <i);
}
- p!(pi: <i str(";") nl {l.expr()});
+ p!(pi: str(";") nl);
+
+ let expr_comments = trivia_between(
+ l.syntax().clone(),
+ l.semi_token().map(Into::into).as_ref(),
+ l.expr()
+ .map(|e| e.syntax().clone())
+ .map(Into::into)
+ .as_ref(),
+ );
+ p!(pi: items(format_comments(&expr_comments, CommentLocation::AboveItem)));
+
+ // TODO: needs_newline_above expr
+ p!(pi: {l.expr()});
pi
}
Expr::ExprIfThenElse(ite) => {
@@ -515,6 +549,25 @@
local unary = !a;
+ local
+ // I am comment
+ singleLocalWithItemComment = 1,
+ ;
+
+ // Comment between local and expression
+
+ local
+ a = 1, // Inline
+ // Comment above b
+ b = 4,
+
+ // c needs some space
+ c = 5,
+
+ // Comment after everything
+ ;
+
+
local Template = {z: "foo"};
{