difftreelog
feat(fmt) preserve comments at root of source code
in: master
3 files changed
cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth1use std::{fmt::Debug, marker::PhantomData, mem};23use jrsonnet_rowan_parser::{4 nodes::{Trivia, TriviaKind},5 AstNode, AstToken, SyntaxElement,6 SyntaxKind::*,7 SyntaxNode, TS,8};910pub type ChildTrivia = Vec<Trivia>;1112pub struct ChildIterator<I, T> {13 inner: I,14 _marker: PhantomData<T>,15}1617pub fn children_between<T: AstNode + Debug>(18 node: SyntaxNode,19 start: Option<&SyntaxElement>,20 end: Option<&SyntaxElement>,21) -> (Vec<Child<T>>, ChildTrivia) {22 let mut iter = node.children_with_tokens().peekable();23 while iter.peek() == start {24 iter.next();25 }26 children(27 iter.take_while(|i| Some(i) != end),28 start.is_none() || end.is_none(),29 )30}3132pub fn should_start_with_newline(tt: &ChildTrivia) -> bool {33 // First for previous item end34 count_newlines_before(&tt) >= 235}3637fn count_newlines_before(tt: &ChildTrivia) -> usize {38 let mut nl_count = 0;39 for t in tt {40 match t.kind() {41 TriviaKind::Whitespace => {42 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();43 }44 _ => break,45 }46 }47 nl_count48}49fn count_newlines_after(tt: &ChildTrivia) -> usize {50 let mut nl_count = 0;51 for t in tt.iter().rev() {52 match t.kind() {53 TriviaKind::Whitespace => {54 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();55 }56 TriviaKind::SingleLineHashComment => {57 nl_count += 1;58 break;59 }60 TriviaKind::SingleLineSlashComment => {61 nl_count += 1;62 break;63 }64 _ => {}65 }66 }67 nl_count68}6970pub fn children<'a, T: AstNode + Debug>(71 items: impl Iterator<Item = SyntaxElement>,72 loose: bool,73) -> (Vec<Child<T>>, ChildTrivia) {74 let mut out = Vec::new();75 let mut current_child = None::<Child<T>>;76 let mut next = ChildTrivia::new();77 // Previous element ended, do not add more inline comments78 let mut started_next = false;79 let mut had_some = false;8081 for item in items {82 if let Some(value) = item.as_node().cloned().and_then(T::cast) {83 let before_trivia = mem::take(&mut next);84 let last_child = current_child.replace(Child {85 newlines_above: if had_some {86 count_newlines_before(&before_trivia)87 + current_child88 .as_ref()89 .map(|c| count_newlines_after(&c.inline_trivia))90 .unwrap_or_default()91 } else {92 093 },94 before_trivia,95 value,96 inline_trivia: Vec::new(),97 });98 if let Some(last_child) = last_child {99 out.push(last_child)100 }101 had_some = true;102 started_next = false;103 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {104 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment105 || trivia.kind() == TriviaKind::SingleLineSlashComment;106 if started_next107 || current_child.is_none()108 || trivia.text().contains('\n') && !is_single_line_comment109 {110 next.push(trivia.clone());111 started_next = true;112 } else {113 let cur = current_child.as_mut().expect("checked not none");114 cur.inline_trivia.push(trivia);115 if is_single_line_comment {116 started_next = true;117 }118 }119 had_some = true;120 } else if loose {121 if had_some {122 break;123 }124 started_next = true;125 } else {126 assert!(127 TS![, ;].contains(item.kind()) || item.kind() == ERROR,128 "silently eaten token: {:?}",129 item.kind()130 )131 }132 }133134 if let Some(current_child) = current_child {135 out.push(current_child);136 }137138 (out, next)139}140141#[derive(Debug)]142pub struct Child<T> {143 newlines_above: usize,144 /// Comment before item, i.e145 ///146 /// ```ignore147 /// // Comment148 /// item149 /// ```150 pub before_trivia: ChildTrivia,151 pub value: T,152 /// Comment after line, but located at same line153 ///154 /// ```ignore155 /// item1, // Inline comment156 /// // Not inline comment157 /// item2,158 /// ```159 pub inline_trivia: ChildTrivia,160}161162impl<T> Child<T> {163 /// If this child has two newlines above in source code, so it needs to have it in output164 pub fn needs_newline_above(&self) -> bool {165 // First line for end of previous item166 self.newlines_above >= 2167 }168}cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -1,6 +1,6 @@
use std::any::type_name;
-use children::children_between;
+use children::{children_between, trivia_before};
use dprint_core::formatting::{PrintItems, PrintOptions};
use jrsonnet_rowan_parser::{
nodes::{
@@ -13,7 +13,7 @@
};
use crate::{
- children::should_start_with_newline,
+ children::{should_start_with_newline, trivia_after},
comments::{format_comments, CommentLocation},
};
@@ -463,8 +463,25 @@
impl Printable for SourceFile {
fn print(&self) -> PrintItems {
- assert!(self.expr().is_some());
- self.expr().print()
+ let mut pi = p!(new:);
+ let before = trivia_before(
+ self.syntax().clone(),
+ self.expr()
+ .map(|e| e.syntax().clone())
+ .map(Into::into)
+ .as_ref(),
+ );
+ let after = trivia_after(
+ self.syntax().clone(),
+ self.expr()
+ .map(|e| e.syntax().clone())
+ .map(Into::into)
+ .as_ref(),
+ );
+ p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));
+ p!(pi: {self.expr()} nl);
+ p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));
+ pi
}
}
@@ -574,6 +591,7 @@
} + Template
+ // Comment after everything
"#,
);
crates/jrsonnet-rowan-parser/src/event.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/event.rs
+++ b/crates/jrsonnet-rowan-parser/src/event.rs
@@ -117,6 +117,9 @@
eat_start_whitespace = false;
}
Event::Finish { wrapper } => {
+ if depth == 1 {
+ self.skip_whitespace();
+ }
self.builder.finish_node();
depth -= 1;
let mut idx = idx;
@@ -126,6 +129,9 @@
wrapper = if let Event::Finish { wrapper } =
mem::replace(&mut self.events[idx], Event::Noop)
{
+ if depth == 1 {
+ self.skip_whitespace();
+ }
self.builder.finish_node();
depth -= 1;
wrapper