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.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/children.rs
+++ b/cmds/jrsonnet-fmt/src/children.rs
@@ -267,3 +267,8 @@
pub should_start_with_newline: bool,
pub trivia: ChildTrivia,
}
+impl EndingComments {
+ pub fn is_empty(&self) -> bool {
+ !self.should_start_with_newline && self.trivia.is_empty()
+ }
+}
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, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal, Member, Name,9 Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text, UnaryOperator,10 Visibility, VisibilityKind,11 },12 rowan::NodeOrToken,13 AstNode, AstToken, SyntaxToken,14};1516use crate::{17 children::{trivia_after, trivia_between},18 comments::{format_comments, CommentLocation},19};2021mod children;22mod comments;23#[cfg(test)]24mod tests;2526pub trait Printable {27 fn print(&self) -> PrintItems;28}2930macro_rules! pi {31 (@i; $($t:tt)*) => {{32 #[allow(unused_mut)]33 let mut o = dprint_core::formatting::PrintItems::new();34 pi!(@s; o: $($t)*);35 o36 }};37 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{38 $o.push_str($e);39 pi!(@s; $o: $($t)*);40 }};41 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{42 $o.push_string($e);43 pi!(@s; $o: $($t)*);44 }};45 (@s; $o:ident: nl $($t:tt)*) => {{46 $o.push_signal(dprint_core::formatting::Signal::NewLine);47 pi!(@s; $o: $($t)*);48 }};49 (@s; $o:ident: tab $($t:tt)*) => {{50 $o.push_signal(dprint_core::formatting::Signal::Tab);51 pi!(@s; $o: $($t)*);52 }};53 (@s; $o:ident: >i $($t:tt)*) => {{54 $o.push_signal(dprint_core::formatting::Signal::StartIndent);55 pi!(@s; $o: $($t)*);56 }};57 (@s; $o:ident: <i $($t:tt)*) => {{58 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);59 pi!(@s; $o: $($t)*);60 }};61 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{62 $o.extend($expr.print());63 pi!(@s; $o: $($t)*);64 }};65 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{66 $o.extend($expr);67 pi!(@s; $o: $($t)*);68 }};69 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{70 if $e {71 pi!(@s; $o: $($then)*);72 }73 pi!(@s; $o: $($t)*);74 }};75 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{76 if $e {77 pi!(@s; $o: $($then)*);78 } else {79 pi!(@s; $o: $($else)*);80 }81 pi!(@s; $o: $($t)*);82 }};83 (@s; $i:ident:) => {}84}85macro_rules! p {86 (new: $($t:tt)*) => {87 pi!(@i; $($t)*)88 };89 ($o:ident: $($t:tt)*) => {90 pi!(@s; $o: $($t)*)91 };92}93pub(crate) use p;94pub(crate) use pi;9596impl<P> Printable for Option<P>97where98 P: Printable,99{100 fn print(&self) -> PrintItems {101 if let Some(v) = self {102 v.print()103 } else {104 p!(new: string(105 format!(106 "/*missing {}*/",107 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")108 ),109 ))110 }111 }112}113114impl Printable for SyntaxToken {115 fn print(&self) -> PrintItems {116 p!(new: string(self.to_string()))117 }118}119120impl Printable for Text {121 fn print(&self) -> PrintItems {122 p!(new: string(format!("{}", self)))123 }124}125impl Printable for Number {126 fn print(&self) -> PrintItems {127 p!(new: string(format!("{}", self)))128 }129}130131impl Printable for Name {132 fn print(&self) -> PrintItems {133 p!(new: {self.ident_lit()})134 }135}136137impl Printable for DestructRest {138 fn print(&self) -> PrintItems {139 let mut pi = p!(new: str("..."));140 if let Some(name) = self.into() {141 p!(pi: {name});142 }143 pi144 }145}146147impl Printable for Destruct {148 fn print(&self) -> PrintItems {149 let mut pi = p!(new:);150 match self {151 Destruct::DestructFull(f) => {152 p!(pi: {f.name()})153 }154 Destruct::DestructSkip(_) => p!(pi: str("?")),155 Destruct::DestructArray(a) => {156 p!(pi: str("[") >i nl);157 for el in a.destruct_array_parts() {158 match el {159 DestructArrayPart::DestructArrayElement(e) => {160 p!(pi: {e.destruct()} str(",") nl)161 }162 DestructArrayPart::DestructRest(d) => {163 p!(pi: {d} str(",") nl)164 }165 }166 }167 p!(pi: <i str("]"));168 }169 Destruct::DestructObject(o) => {170 p!(pi: str("{") >i nl);171 for item in o.destruct_object_fields() {172 p!(pi: {item.field()});173 if let Some(des) = item.destruct() {174 p!(pi: str(": ") {des})175 }176 if let Some(def) = item.expr() {177 p!(pi: str(" = ") {def});178 }179 p!(pi: str(",") nl);180 }181 if let Some(rest) = o.destruct_rest() {182 p!(pi: {rest} nl)183 }184 p!(pi: <i str("}"));185 }186 }187 pi188 }189}190191impl Printable for FieldName {192 fn print(&self) -> PrintItems {193 match self {194 FieldName::FieldNameFixed(f) => {195 if let Some(id) = f.id() {196 p!(new: {id})197 } else if let Some(str) = f.text() {198 p!(new: {str})199 } else {200 p!(new: str("/*missing FieldName*/"))201 }202 }203 FieldName::FieldNameDynamic(d) => {204 p!(new: str("[") {d.expr()} str("]"))205 }206 }207 }208}209210impl Printable for Visibility {211 fn print(&self) -> PrintItems {212 p!(new: string(self.to_string()))213 }214}215216impl Printable for ObjLocal {217 fn print(&self) -> PrintItems {218 p!(new: str("local ") {self.bind()})219 }220}221222impl Printable for Assertion {223 fn print(&self) -> PrintItems {224 let mut pi = p!(new: str("assert ") {self.condition()});225 if self.colon_token().is_some() || self.message().is_some() {226 p!(pi: str(": ") {self.message()})227 }228 pi229 }230}231232impl Printable for ParamsDesc {233 fn print(&self) -> PrintItems {234 let mut pi = p!(new: str("(") >i nl);235 for param in self.params() {236 p!(pi: {param.destruct()});237 if param.assign_token().is_some() || param.expr().is_some() {238 p!(pi: str(" = ") {param.expr()})239 }240 p!(pi: str(",") nl)241 }242 p!(pi: <i str(")"));243 pi244 }245}246impl Printable for ArgsDesc {247 fn print(&self) -> PrintItems {248 let mut pi = p!(new: str("(") >i nl);249 for arg in self.args() {250 if arg.name().is_some() || arg.assign_token().is_some() {251 p!(pi: {arg.name()} str(" = "));252 }253 p!(pi: {arg.expr()} str(",") nl)254 }255 p!(pi: <i str(")"));256 pi257 }258}259impl Printable for SliceDesc {260 fn print(&self) -> PrintItems {261 let mut pi = p!(new: str("["));262 if self.from().is_some() {263 p!(pi: {self.from()});264 }265 p!(pi: str(":"));266 if self.end().is_some() {267 p!(pi: {self.end().map(|e|e.expr())})268 }269 // Keep only one : in case if we don't need step270 if self.step().is_some() {271 p!(pi: str(":") {self.step().map(|e|e.expr())});272 }273 p!(pi: str("]"));274 pi275 }276}277278impl Printable for Member {279 fn print(&self) -> PrintItems {280 match self {281 Member::MemberBindStmt(b) => {282 p!(new: {b.obj_local()})283 }284 Member::MemberAssertStmt(ass) => {285 p!(new: {ass.assertion()})286 }287 Member::MemberFieldNormal(n) => {288 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})289 }290 Member::MemberFieldMethod(_) => todo!(),291 }292 }293}294295impl Printable for ObjBody {296 fn print(&self) -> PrintItems {297 match self {298 ObjBody::ObjBodyComp(l) => {299 let (children, end_comments) = children_between::<Member>(300 l.syntax().clone(),301 l.l_brace_token().map(Into::into).as_ref(),302 Some(303 &(l.comp_specs()304 .next()305 .expect("at least one spec is defined")306 .syntax()307 .clone())308 .into(),309 ),310 );311 let mut pi = p!(new: str("{") >i nl);312 for mem in children.into_iter() {313 if mem.should_start_with_newline {314 p!(pi: nl);315 }316 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));317 p!(pi: {mem.value} str(","));318 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));319 p!(pi: nl)320 }321322 if end_comments.should_start_with_newline {323 p!(pi: nl);324 }325 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));326327 let (compspecs, end_comments) = children_between::<CompSpec>(328 l.syntax().clone(),329 l.member_comps()330 .last()331 .map(|m| m.syntax().clone())332 .map(Into::into)333 .or_else(|| l.l_brace_token().map(Into::into))334 .as_ref(),335 l.r_brace_token().map(Into::into).as_ref(),336 );337 for mem in compspecs.into_iter() {338 if mem.should_start_with_newline {339 p!(pi: nl);340 }341 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));342 p!(pi: {mem.value});343 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));344 }345 if end_comments.should_start_with_newline {346 p!(pi: nl);347 }348 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));349350 p!(pi: <i str("}"));351 pi352 }353 ObjBody::ObjBodyMemberList(l) => {354 let (children, end_comments) = children_between::<Member>(355 l.syntax().clone(),356 l.l_brace_token().map(Into::into).as_ref(),357 l.r_brace_token().map(Into::into).as_ref(),358 );359 if children.is_empty() && end_comments.is_empty() {360 return p!(new: str("{ }"));361 }362 let mut pi = p!(new: str("{") >i nl);363 for mem in children.into_iter() {364 if mem.should_start_with_newline {365 p!(pi: nl);366 }367 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));368 p!(pi: {mem.value} str(","));369 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));370 p!(pi: nl)371 }372373 if end_comments.should_start_with_newline {374 p!(pi: nl);375 }376 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));377 p!(pi: <i str("}"));378 pi379 }380 }381 }382}383impl Printable for UnaryOperator {384 fn print(&self) -> PrintItems {385 p!(new: string(self.text().to_string()))386 }387}388impl Printable for BinaryOperator {389 fn print(&self) -> PrintItems {390 p!(new: string(self.text().to_string()))391 }392}393impl Printable for Bind {394 fn print(&self) -> PrintItems {395 match self {396 Bind::BindDestruct(d) => {397 p!(new: {d.into()} str(" = ") {d.value()})398 }399 Bind::BindFunction(f) => {400 p!(new: {f.name()} {f.params()} str(" = ") {f.value()})401 }402 }403 }404}405impl Printable for Literal {406 fn print(&self) -> PrintItems {407 p!(new: string(self.syntax().to_string()))408 }409}410impl Printable for ImportKind {411 fn print(&self) -> PrintItems {412 p!(new: string(self.syntax().to_string()))413 }414}415impl Printable for LhsExpr {416 fn print(&self) -> PrintItems {417 p!(new: {self.expr()})418 }419}420impl Printable for ForSpec {421 fn print(&self) -> PrintItems {422 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})423 }424}425impl Printable for IfSpec {426 fn print(&self) -> PrintItems {427 p!(new: str("if ") {self.expr()})428 }429}430impl Printable for CompSpec {431 fn print(&self) -> PrintItems {432 match self {433 CompSpec::ForSpec(f) => f.print(),434 CompSpec::IfSpec(i) => i.print(),435 }436 }437}438impl Printable for Expr {439 fn print(&self) -> PrintItems {440 match self {441 Expr::ExprBinary(b) => {442 p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})443 }444 Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),445 Expr::ExprSlice(s) => {446 p!(new: {s.expr()} {s.slice_desc()})447 }448 Expr::ExprIndex(i) => {449 p!(new: {i.expr()} str(".") {i.index()})450 }451 Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),452 Expr::ExprApply(a) => {453 let mut pi = p!(new: {a.expr()} {a.args_desc()});454 if a.tailstrict_kw_token().is_some() {455 p!(pi: str(" tailstrict"));456 }457 pi458 }459 Expr::ExprObjExtend(ex) => {460 p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})461 }462 Expr::ExprParened(p) => {463 p!(new: str("(") {p.expr()} str(")"))464 }465 Expr::ExprString(s) => p!(new: {s.text()}),466 Expr::ExprNumber(n) => p!(new: {n.number()}),467 Expr::ExprArray(a) => {468 let mut pi = p!(new: str("[") >i nl);469 for el in a.exprs() {470 p!(pi: {el} str(",") nl);471 }472 p!(pi: <i str("]"));473 pi474 }475 Expr::ExprObject(o) => {476 p!(new: {o.obj_body()})477 }478 Expr::ExprArrayComp(arr) => {479 let mut pi = p!(new: str("[") {arr.expr()});480 for spec in arr.comp_specs() {481 p!(pi: str(" ") {spec});482 }483 p!(pi: str("]"));484 pi485 }486 Expr::ExprImport(v) => {487 p!(new: {v.import_kind()} str(" ") {v.text()})488 }489 Expr::ExprVar(n) => p!(new: {n.name()}),490 Expr::ExprLocal(l) => {491 let mut pi = p!(new:);492 let (binds, end_comments) = children_between::<Bind>(493 l.syntax().clone(),494 l.local_kw_token().map(Into::into).as_ref(),495 l.semi_token().map(Into::into).as_ref(),496 );497 if binds.len() == 1 {498 let bind = &binds[0];499 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));500 p!(pi: str("local ") {bind.value});501 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?502 } else {503 p!(pi: str("local") >i nl);504 for bind in binds {505 if bind.should_start_with_newline {506 p!(pi: nl);507 }508 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));509 p!(pi: {bind.value} str(","));510 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);511 }512 if end_comments.should_start_with_newline {513 p!(pi: nl)514 }515 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));516 p!(pi: <i);517 }518 p!(pi: str(";") nl);519520 let expr_comments = trivia_between(521 l.syntax().clone(),522 l.semi_token().map(Into::into).as_ref(),523 l.expr()524 .map(|e| e.syntax().clone())525 .map(Into::into)526 .as_ref(),527 );528529 if expr_comments.should_start_with_newline {530 p!(pi: nl);531 }532 p!(pi: items(format_comments(&expr_comments.trivia, CommentLocation::AboveItem)));533 p!(pi: {l.expr()});534 pi535 }536 Expr::ExprIfThenElse(ite) => {537 let mut pi =538 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});539 if ite.else_kw_token().is_some() || ite.else_().is_some() {540 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})541 }542 pi543 }544 Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),545 Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),546 Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),547 Expr::ExprLiteral(l) => {548 p!(new: {l.literal()})549 }550 }551 }552}553554impl Printable for SourceFile {555 fn print(&self) -> PrintItems {556 let mut pi = p!(new:);557 let before = trivia_before(558 self.syntax().clone(),559 self.expr()560 .map(|e| e.syntax().clone())561 .map(Into::into)562 .as_ref(),563 );564 let after = trivia_after(565 self.syntax().clone(),566 self.expr()567 .map(|e| e.syntax().clone())568 .map(Into::into)569 .as_ref(),570 );571 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));572 p!(pi: {self.expr()} nl);573 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));574 pi575 }576}577578fn format(input: &str) -> String {579 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);580 if !errors.is_empty() {581 let mut builder = ass_stroke::SnippetBuilder::new(input);582 for error in errors {583 builder584 .error(ass_stroke::Text::single(585 format!("{:?}", error.error).chars(),586 Default::default(),587 ))588 .range(589 error.range.start().into()590 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),591 )592 .build();593 }594 let snippet = builder.build();595 let ansi = ass_stroke::source_to_ansi(&snippet);596 println!("{ansi}");597 }598 dprint_core::formatting::format(599 || parsed.print(),600 PrintOptions {601 indent_width: 2,602 max_width: 100,603 use_tabs: false,604 new_line_text: "\n",605 },606 )607}608fn main() {609 let input = r#"610611612 # Edit me!613 local b = import "b.libsonnet"; # comment614 local a = import "a.libsonnet";615616 local f(x,y)=x+y;617618 local {a: [b, ..., c], d, ...e} = null;619620 local ass = assert false : false; false;621622 local fn = function(a, b, c = 3) 4;623624 local comp = [a for b in c if d == e];625 local ocomp = {[k]: 1 for k in v};626627 local ? = skip;628629 local ie = a[expr];630631 local unary = !a;632633 local634 // I am comment635 singleLocalWithItemComment = 1,636 ;637638 // Comment between local and expression639640 local641 a = 1, // Inline642 // Comment above b643 b = 4,644645 // c needs some space646 c = 5,647648 // Comment after everything649 ;650651652 local Template = {z: "foo"};653654 {655 local656657 h = 3,658 assert self.a == 1659660 : "error",661 "f": ((((((3)))))) ,662 "g g":663 f(4,2),664 arr: [[665 1, 2,666 ],667 3,668 {669 b: {670 c: {671 k: [16]672 }673 }674 }675 ],676 m: a[1::],677 m: b[::],678679 comments: {680 _: '',681 // Plain comment682 a: '',683684 # Plain comment with empty line before685 b: '',686 /*Single-line multiline comment687688 */689 c: '',690691 /**Single-line multiline doc comment692693 */694 c: '',695696 /**multiline doc comment697 s698 */699 c: '',700701 /*702703 Multi-line704705 comment706 */707 d: '',708709 e: '', // Inline comment710711 k: '',712713 // Text after everything714 },715 comments2: {716 k: '',717 // Text after everything, but no newline above718 },719 k: if a == b then720721722 2723724 else Template {},725726 compspecs: {727 obj_with_no_item: {a:1, for i in [1, 2, 3]},728 obj_with_2_items: {a:1, /*b:2,*/ for i in [1,2,3]},729 }730731 } + Template732"#;733734 let mut iteration = 0;735 let mut a = input.to_string();736 let mut b;737 // https://github.com/dprint/dprint/pull/423738 loop {739 b = format(&a).trim().to_owned();740 if a == b {741 break;742 }743 println!("{b}");744 a = b;745 iteration += 1;746 if iteration > 5 {747 panic!("formatting not converged");748 break;749 }750 }751 println!("{a}");752}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" }