difftreelog
feat(fmt) try to use ass-stroke
in: master
4 files changed
cmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet-fmt/Cargo.toml
+++ b/cmds/jrsonnet-fmt/Cargo.toml
@@ -8,3 +8,4 @@
jrsonnet-rowan-parser.workspace = true
insta = "1.15"
indoc = "1.0"
+ass-stroke = { path = "/home/lach/build/ass-stroke/crates/ass-stroke", version = "0.1.0" }
cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth1// TODO: Return errors as trivia23use std::{fmt::Debug, mem};45use jrsonnet_rowan_parser::{6 nodes::{CustomError, Trivia, TriviaKind},7 AstNode, AstToken, SyntaxElement, SyntaxNode, TS,8};910pub type ChildTrivia = Vec<Result<Trivia, String>>;1112/// Node should have no non-trivia tokens before element13pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {14 let mut out = Vec::new();15 for item in node.children_with_tokens() {16 if Some(&item) == end {17 break;18 }1920 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {21 out.push(Ok(trivia));22 } else if CustomError::can_cast(item.kind()) {23 out.push(Err(item.to_string()));24 } else if end.is_none() {25 break;26 } else {27 assert!(28 TS![, ;].contains(item.kind()),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(Ok(trivia));50 } else if CustomError::can_cast(item.kind()) {51 out.push(Err(item.to_string()))52 } else {53 assert!(54 TS![, ;].contains(item.kind()),55 "silently eaten token: {:?}",56 item.kind()57 )58 }59 }60 out61}6263pub fn trivia_between(64 node: SyntaxNode,65 start: Option<&SyntaxElement>,66 end: Option<&SyntaxElement>,67) -> EndingComments {68 let mut iter = node.children_with_tokens().peekable();69 while iter.peek() != start {70 iter.next();71 }72 iter.next();7374 let loose = start.is_none() || end.is_none();7576 let mut out = Vec::new();77 for item in iter.take_while(|i| Some(i) != end) {78 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {79 out.push(Ok(trivia));80 } else if CustomError::can_cast(item.kind()) {81 out.push(Err(item.to_string()))82 } else if loose {83 break;84 } else {85 assert!(86 TS![, ;].contains(item.kind()),87 "silently eaten token: {:?}",88 item.kind()89 )90 }91 }92 EndingComments {93 should_start_with_newline: should_start_with_newline(None, &out),94 trivia: out,95 }96}9798pub fn children_between<T: AstNode + Debug>(99 node: SyntaxNode,100 start: Option<&SyntaxElement>,101 end: Option<&SyntaxElement>,102) -> (Vec<Child<T>>, EndingComments) {103 let mut iter = node.children_with_tokens().peekable();104 while iter.peek() != start {105 iter.next();106 }107 iter.next();108 children(109 iter.take_while(|i| Some(i) != end),110 start.is_none() || end.is_none(),111 )112}113114pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {115 count_newlines_before(tt)116 + prev_inline117 .map(count_newlines_after)118 .unwrap_or_default()119120 // First for previous item end, second for current item121 >= 2122}123124fn count_newlines_before(tt: &ChildTrivia) -> usize {125 let mut nl_count = 0;126 for t in tt {127 match t {128 Ok(t) => match t.kind() {129 TriviaKind::Whitespace => {130 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();131 }132 _ => break,133 },134 Err(_) => {135 nl_count += 1;136 }137 }138 }139 nl_count140}141fn count_newlines_after(tt: &ChildTrivia) -> usize {142 let mut nl_count = 0;143 for t in tt.iter().rev() {144 match t {145 Ok(t) => match t.kind() {146 TriviaKind::Whitespace => {147 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();148 }149 TriviaKind::SingleLineHashComment => {150 nl_count += 1;151 break;152 }153 TriviaKind::SingleLineSlashComment => {154 nl_count += 1;155 break;156 }157 _ => {}158 },159 Err(_) => nl_count += 1,160 }161 }162 nl_count163}164165pub fn children<T: AstNode + Debug>(166 items: impl Iterator<Item = SyntaxElement>,167 loose: bool,168) -> (Vec<Child<T>>, EndingComments) {169 let mut out = Vec::new();170 let mut current_child = None::<Child<T>>;171 let mut next = ChildTrivia::new();172 // Previous element ended, do not add more inline comments173 let mut started_next = false;174 let mut had_some = false;175176 for item in items {177 if let Some(value) = item.as_node().cloned().and_then(T::cast) {178 let before_trivia = mem::take(&mut next);179 let last_child = current_child.replace(Child {180 // First item should not start with newline181 should_start_with_newline: had_some182 && should_start_with_newline(183 current_child.as_ref().map(|c| &c.inline_trivia),184 &before_trivia,185 ),186 before_trivia,187 value,188 inline_trivia: Vec::new(),189 });190 if let Some(last_child) = last_child {191 out.push(last_child)192 }193 had_some = true;194 started_next = false;195 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {196 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment197 || trivia.kind() == TriviaKind::SingleLineSlashComment;198 if started_next199 || current_child.is_none()200 || trivia.text().contains('\n') && !is_single_line_comment201 {202 next.push(Ok(trivia.clone()));203 started_next = true;204 } else {205 let cur = current_child.as_mut().expect("checked not none");206 cur.inline_trivia.push(Ok(trivia));207 if is_single_line_comment {208 started_next = true;209 }210 }211 had_some = true;212 } else if CustomError::can_cast(item.kind()) {213 next.push(Err(item.to_string()))214 } else if loose {215 if had_some {216 break;217 }218 started_next = true;219 } else {220 assert!(221 TS![, ;].contains(item.kind()),222 "silently eaten token: {:?}",223 item.kind()224 )225 }226 }227228 let ending_comments = EndingComments {229 should_start_with_newline: should_start_with_newline(230 current_child.as_ref().map(|c| &c.inline_trivia),231 &next,232 ),233 trivia: next,234 };235236 if let Some(current_child) = current_child {237 out.push(current_child);238 }239240 (out, ending_comments)241}242243#[derive(Debug)]244pub struct Child<T> {245 /// If this child has two newlines above in source code, so it needs to have it in the output246 pub should_start_with_newline: bool,247 /// Comment before item, i.e248 ///249 /// ```ignore250 /// // Comment251 /// item252 /// ```253 pub before_trivia: ChildTrivia,254 pub value: T,255 /// Comment after line, but located at same line256 ///257 /// ```ignore258 /// item1, // Inline comment259 /// // Not inline comment260 /// item2,261 /// ```262 pub inline_trivia: ChildTrivia,263}264265pub struct EndingComments {266 /// If this child has two newlines above in source code, so it needs to have it in the output267 pub should_start_with_newline: bool,268 pub trivia: ChildTrivia,269}cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/main.rs
+++ b/cmds/jrsonnet-fmt/src/main.rs
@@ -296,7 +296,6 @@
fn print(&self) -> PrintItems {
match self {
ObjBody::ObjBodyComp(l) => {
- let mut pi = p!(new: str("{") >i nl);
let (children, end_comments) = children_between::<Member>(
l.syntax().clone(),
l.l_brace_token().map(Into::into).as_ref(),
@@ -309,6 +308,7 @@
.into(),
),
);
+ let mut pi = p!(new: str("{") >i nl);
for mem in children.into_iter() {
if mem.should_start_with_newline {
p!(pi: nl);
@@ -341,7 +341,6 @@
p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));
p!(pi: {mem.value});
p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));
- p!(pi: nl)
}
if end_comments.should_start_with_newline {
p!(pi: nl);
@@ -352,12 +351,15 @@
pi
}
ObjBody::ObjBodyMemberList(l) => {
- let mut pi = p!(new: str("{") >i nl);
let (children, end_comments) = children_between::<Member>(
l.syntax().clone(),
l.l_brace_token().map(Into::into).as_ref(),
l.r_brace_token().map(Into::into).as_ref(),
);
+ if children.is_empty() && end_comments.is_empty() {
+ return p!(new: str("{ }"));
+ }
+ let mut pi = p!(new: str("{") >i nl);
for mem in children.into_iter() {
if mem.should_start_with_newline {
p!(pi: nl);
@@ -395,7 +397,7 @@
p!(new: {d.into()} str(" = ") {d.value()})
}
Bind::BindFunction(f) => {
- p!(new: str("function") {f.params()} str(" = ") {f.value()})
+ p!(new: {f.name()} {f.params()} str(" = ") {f.value()})
}
}
}
@@ -504,7 +506,7 @@
p!(pi: nl);
}
p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));
- p!(pi: {bind.value} str(";"));
+ p!(pi: {bind.value} str(","));
p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);
}
if end_comments.should_start_with_newline {
@@ -573,16 +575,45 @@
}
}
+fn format(input: &str) -> String {
+ let (parsed, errors) = jrsonnet_rowan_parser::parse(input);
+ if !errors.is_empty() {
+ let mut builder = ass_stroke::SnippetBuilder::new(input);
+ for error in errors {
+ builder
+ .error(ass_stroke::Text::single(
+ format!("{:?}", error.error).chars(),
+ Default::default(),
+ ))
+ .range(
+ error.range.start().into()
+ ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),
+ )
+ .build();
+ }
+ let snippet = builder.build();
+ let ansi = ass_stroke::source_to_ansi(&snippet);
+ println!("{ansi}");
+ }
+ dprint_core::formatting::format(
+ || parsed.print(),
+ PrintOptions {
+ indent_width: 2,
+ max_width: 100,
+ use_tabs: false,
+ new_line_text: "\n",
+ },
+ )
+}
fn main() {
- let (parsed, _errors) = jrsonnet_rowan_parser::parse(
- r#"
+ let input = r#"
# Edit me!
local b = import "b.libsonnet"; # comment
local a = import "a.libsonnet";
- local f(x,y)=x+y;
+ local f(x,y)=x+y;
local {a: [b, ..., c], d, ...e} = null;
@@ -693,28 +724,29 @@
else Template {},
compspecs: {
- obj_with_no_item: {for i in [1, 2, 3]},
- obj_with_2_items: {a:1, b:2, for i in [1,2,3]},
+ obj_with_no_item: {a:1, for i in [1, 2, 3]},
+ obj_with_2_items: {a:1, /*b:2,*/ for i in [1,2,3]},
}
} + Template
+"#;
-
- // Comment after everything
-"#,
- );
-
- // dbg!(errors);
- dbg!(&parsed);
-
- let o = dprint_core::formatting::format(
- || parsed.print(),
- PrintOptions {
- indent_width: 2,
- max_width: 100,
- use_tabs: false,
- new_line_text: "\n",
- },
- );
- println!("{}", o);
+ let mut iteration = 0;
+ let mut a = input.to_string();
+ let mut b;
+ // https://github.com/dprint/dprint/pull/423
+ loop {
+ b = format(&a).trim().to_owned();
+ if a == b {
+ break;
+ }
+ println!("{b}");
+ a = b;
+ iteration += 1;
+ if iteration > 5 {
+ panic!("formatting not converged");
+ break;
+ }
+ }
+ println!("{a}");
}
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -12,10 +12,7 @@
# Use mimalloc as allocator
mimalloc = ["mimallocator"]
# Experimental feature, which allows to preserve order of object fields
-exp-preserve-order = [
- "jrsonnet-evaluator/exp-preserve-order",
- "jrsonnet-cli/exp-preserve-order",
-]
+exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order", "jrsonnet-cli/exp-preserve-order"]
# Destructuring of locals
exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
# Iteration over objects yields [key, value] elements
@@ -44,3 +41,4 @@
clap_complete = { version = "4.1" }
serde_json = "1.0.104"
serde = { workspace = true, features = ["derive"] }
+ass-stroke = { git = "https://github.com/CertainLach/ass-stroke", version = "0.1.0" }