difftreelog
fix(fmt) multiline comment whitespace handling
in: master
2 files changed
cmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/comments.rs
+++ b/cmds/jrsonnet-fmt/src/comments.rs
@@ -72,7 +72,7 @@
if matches!(loc, CommentLocation::ItemInline) {
p!(pi: str(" "));
}
- p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */"))
+ p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */") nl)
} else if !lines.is_empty() {
fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
let offset = a
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth1use std::{2 any::type_name,3 fs,4 io::{self, Write},5 path::PathBuf,6 process,7};89use children::{children_between, trivia_before};10use clap::Parser;11use dprint_core::formatting::{PrintItems, PrintOptions};12use jrsonnet_rowan_parser::{13 nodes::{14 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,17 UnaryOperator, Visibility,18 },19 AstNode, AstToken, SyntaxToken,20};2122use crate::{23 children::{trivia_after, trivia_between},24 comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29#[cfg(test)]30mod tests;3132pub trait Printable {33 fn print(&self) -> PrintItems;34}3536macro_rules! pi {37 (@i; $($t:tt)*) => {{38 #[allow(unused_mut)]39 let mut o = dprint_core::formatting::PrintItems::new();40 pi!(@s; o: $($t)*);41 o42 }};43 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{44 $o.push_str($e);45 pi!(@s; $o: $($t)*);46 }};47 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{48 $o.push_string($e);49 pi!(@s; $o: $($t)*);50 }};51 (@s; $o:ident: nl $($t:tt)*) => {{52 $o.push_signal(dprint_core::formatting::Signal::NewLine);53 pi!(@s; $o: $($t)*);54 }};55 (@s; $o:ident: tab $($t:tt)*) => {{56 $o.push_signal(dprint_core::formatting::Signal::Tab);57 pi!(@s; $o: $($t)*);58 }};59 (@s; $o:ident: >i $($t:tt)*) => {{60 $o.push_signal(dprint_core::formatting::Signal::StartIndent);61 pi!(@s; $o: $($t)*);62 }};63 (@s; $o:ident: <i $($t:tt)*) => {{64 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);65 pi!(@s; $o: $($t)*);66 }};67 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{68 $o.extend($expr.print());69 pi!(@s; $o: $($t)*);70 }};71 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{72 $o.extend($expr);73 pi!(@s; $o: $($t)*);74 }};75 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{76 if $e {77 pi!(@s; $o: $($then)*);78 }79 pi!(@s; $o: $($t)*);80 }};81 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{82 if $e {83 pi!(@s; $o: $($then)*);84 } else {85 pi!(@s; $o: $($else)*);86 }87 pi!(@s; $o: $($t)*);88 }};89 (@s; $i:ident:) => {}90}91macro_rules! p {92 (new: $($t:tt)*) => {93 pi!(@i; $($t)*)94 };95 ($o:ident: $($t:tt)*) => {96 pi!(@s; $o: $($t)*)97 };98}99pub(crate) use p;100pub(crate) use pi;101102impl<P> Printable for Option<P>103where104 P: Printable,105{106 fn print(&self) -> PrintItems {107 if let Some(v) = self {108 v.print()109 } else {110 p!(new: string(111 format!(112 "/*missing {}*/",113 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")114 ),115 ))116 }117 }118}119120impl Printable for SyntaxToken {121 fn print(&self) -> PrintItems {122 p!(new: string(self.to_string()))123 }124}125126impl Printable for Text {127 fn print(&self) -> PrintItems {128 p!(new: string(format!("{}", self)))129 }130}131impl Printable for Number {132 fn print(&self) -> PrintItems {133 p!(new: string(format!("{}", self)))134 }135}136137impl Printable for Name {138 fn print(&self) -> PrintItems {139 p!(new: {self.ident_lit()})140 }141}142143impl Printable for DestructRest {144 fn print(&self) -> PrintItems {145 let mut pi = p!(new: str("..."));146 if let Some(name) = self.into() {147 p!(pi: {name});148 }149 pi150 }151}152153impl Printable for Destruct {154 fn print(&self) -> PrintItems {155 let mut pi = p!(new:);156 match self {157 Destruct::DestructFull(f) => {158 p!(pi: {f.name()})159 }160 Destruct::DestructSkip(_) => p!(pi: str("?")),161 Destruct::DestructArray(a) => {162 p!(pi: str("[") >i nl);163 for el in a.destruct_array_parts() {164 match el {165 DestructArrayPart::DestructArrayElement(e) => {166 p!(pi: {e.destruct()} str(",") nl)167 }168 DestructArrayPart::DestructRest(d) => {169 p!(pi: {d} str(",") nl)170 }171 }172 }173 p!(pi: <i str("]"));174 }175 Destruct::DestructObject(o) => {176 p!(pi: str("{") >i nl);177 for item in o.destruct_object_fields() {178 p!(pi: {item.field()});179 if let Some(des) = item.destruct() {180 p!(pi: str(": ") {des})181 }182 if let Some(def) = item.expr() {183 p!(pi: str(" = ") {def});184 }185 p!(pi: str(",") nl);186 }187 if let Some(rest) = o.destruct_rest() {188 p!(pi: {rest} nl)189 }190 p!(pi: <i str("}"));191 }192 }193 pi194 }195}196197impl Printable for FieldName {198 fn print(&self) -> PrintItems {199 match self {200 FieldName::FieldNameFixed(f) => {201 if let Some(id) = f.id() {202 p!(new: {id})203 } else if let Some(str) = f.text() {204 p!(new: {str})205 } else {206 p!(new: str("/*missing FieldName*/"))207 }208 }209 FieldName::FieldNameDynamic(d) => {210 p!(new: str("[") {d.expr()} str("]"))211 }212 }213 }214}215216impl Printable for Visibility {217 fn print(&self) -> PrintItems {218 p!(new: string(self.to_string()))219 }220}221222impl Printable for ObjLocal {223 fn print(&self) -> PrintItems {224 p!(new: str("local ") {self.bind()})225 }226}227228impl Printable for Assertion {229 fn print(&self) -> PrintItems {230 let mut pi = p!(new: str("assert ") {self.condition()});231 if self.colon_token().is_some() || self.message().is_some() {232 p!(pi: str(": ") {self.message()})233 }234 pi235 }236}237238impl Printable for ParamsDesc {239 fn print(&self) -> PrintItems {240 let mut pi = p!(new: str("(") >i nl);241 for param in self.params() {242 p!(pi: {param.destruct()});243 if param.assign_token().is_some() || param.expr().is_some() {244 p!(pi: str(" = ") {param.expr()})245 }246 p!(pi: str(",") nl)247 }248 p!(pi: <i str(")"));249 pi250 }251}252impl Printable for ArgsDesc {253 fn print(&self) -> PrintItems {254 let mut pi = p!(new: str("(") >i nl);255 for arg in self.args() {256 if arg.name().is_some() || arg.assign_token().is_some() {257 p!(pi: {arg.name()} str(" = "));258 }259 p!(pi: {arg.expr()} str(",") nl)260 }261 p!(pi: <i str(")"));262 pi263 }264}265impl Printable for SliceDesc {266 fn print(&self) -> PrintItems {267 let mut pi = p!(new: str("["));268 if self.from().is_some() {269 p!(pi: {self.from()});270 }271 p!(pi: str(":"));272 if self.end().is_some() {273 p!(pi: {self.end().map(|e|e.expr())})274 }275 // Keep only one : in case if we don't need step276 if self.step().is_some() {277 p!(pi: str(":") {self.step().map(|e|e.expr())});278 }279 p!(pi: str("]"));280 pi281 }282}283284impl Printable for Member {285 fn print(&self) -> PrintItems {286 match self {287 Member::MemberBindStmt(b) => {288 p!(new: {b.obj_local()})289 }290 Member::MemberAssertStmt(ass) => {291 p!(new: {ass.assertion()})292 }293 Member::MemberFieldNormal(n) => {294 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})295 }296 Member::MemberFieldMethod(m) => {297 p!(new: {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})298 }299 }300 }301}302303impl Printable for ObjBody {304 fn print(&self) -> PrintItems {305 match self {306 ObjBody::ObjBodyComp(l) => {307 let (children, mut end_comments) = children_between::<Member>(308 l.syntax().clone(),309 l.l_brace_token().map(Into::into).as_ref(),310 Some(311 &(l.comp_specs()312 .next()313 .expect("at least one spec is defined")314 .syntax()315 .clone())316 .into(),317 ),318 None,319 );320 let trailing_for_comp = end_comments.extract_trailing();321 let mut pi = p!(new: str("{") >i nl);322 for mem in children.into_iter() {323 if mem.should_start_with_newline {324 p!(pi: nl);325 }326 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));327 p!(pi: {mem.value} str(","));328 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));329 p!(pi: nl)330 }331332 if end_comments.should_start_with_newline {333 p!(pi: nl);334 }335 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));336337 let (compspecs, end_comments) = children_between::<CompSpec>(338 l.syntax().clone(),339 l.member_comps()340 .last()341 .map(|m| m.syntax().clone())342 .map(Into::into)343 .or_else(|| l.l_brace_token().map(Into::into))344 .as_ref(),345 l.r_brace_token().map(Into::into).as_ref(),346 Some(trailing_for_comp),347 );348 for mem in compspecs.into_iter() {349 if mem.should_start_with_newline {350 p!(pi: nl);351 }352 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));353 p!(pi: {mem.value});354 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));355 }356 if end_comments.should_start_with_newline {357 p!(pi: nl);358 }359 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));360361 p!(pi: nl <i str("}"));362 pi363 }364 ObjBody::ObjBodyMemberList(l) => {365 let (children, end_comments) = children_between::<Member>(366 l.syntax().clone(),367 l.l_brace_token().map(Into::into).as_ref(),368 l.r_brace_token().map(Into::into).as_ref(),369 None,370 );371 if children.is_empty() && end_comments.is_empty() {372 return p!(new: str("{ }"));373 }374 let mut pi = p!(new: str("{") >i nl);375 for mem in children.into_iter() {376 if mem.should_start_with_newline {377 p!(pi: nl);378 }379 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));380 p!(pi: {mem.value} str(","));381 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));382 p!(pi: nl)383 }384385 if end_comments.should_start_with_newline {386 p!(pi: nl);387 }388 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));389 p!(pi: <i str("}"));390 pi391 }392 }393 }394}395impl Printable for UnaryOperator {396 fn print(&self) -> PrintItems {397 p!(new: string(self.text().to_string()))398 }399}400impl Printable for BinaryOperator {401 fn print(&self) -> PrintItems {402 p!(new: string(self.text().to_string()))403 }404}405impl Printable for Bind {406 fn print(&self) -> PrintItems {407 match self {408 Bind::BindDestruct(d) => {409 p!(new: {d.into()} str(" = ") {d.value()})410 }411 Bind::BindFunction(f) => {412 p!(new: {f.name()} {f.params()} str(" = ") {f.value()})413 }414 }415 }416}417impl Printable for Literal {418 fn print(&self) -> PrintItems {419 p!(new: string(self.syntax().to_string()))420 }421}422impl Printable for ImportKind {423 fn print(&self) -> PrintItems {424 p!(new: string(self.syntax().to_string()))425 }426}427impl Printable for ForSpec {428 fn print(&self) -> PrintItems {429 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})430 }431}432impl Printable for IfSpec {433 fn print(&self) -> PrintItems {434 p!(new: str("if ") {self.expr()})435 }436}437impl Printable for CompSpec {438 fn print(&self) -> PrintItems {439 match self {440 CompSpec::ForSpec(f) => f.print(),441 CompSpec::IfSpec(i) => i.print(),442 }443 }444}445impl Printable for Expr {446 fn print(&self) -> PrintItems {447 let mut o = p!(new:);448 let (stmts, ending) = children_between::<Stmt>(449 self.syntax().clone(),450 None,451 self.expr_base()452 .as_ref()453 .map(ExprBase::syntax)454 .cloned()455 .map(Into::into)456 .as_ref(),457 None,458 );459 for stmt in stmts {460 p!(o: {stmt.value});461 }462 p!(o: {self.expr_base()});463 let (suffixes, ending) = children_between::<Suffix>(464 self.syntax().clone(),465 self.expr_base()466 .as_ref()467 .map(ExprBase::syntax)468 .cloned()469 .map(Into::into)470 .as_ref(),471 None,472 None,473 );474 for suffix in suffixes {475 p!(o: {suffix.value});476 }477 o478 }479}480impl Printable for Suffix {481 fn print(&self) -> PrintItems {482 let mut o = p!(new:);483 match self {484 Suffix::SuffixIndex(i) => {485 if i.question_mark_token().is_some() {486 p!(o: str("?"));487 }488 p!(o: str(".") {i.index()});489 }490 Suffix::SuffixIndexExpr(e) => {491 if e.question_mark_token().is_some() {492 p!(o: str(".?"));493 }494 p!(o: str("[") {e.index()} str("]"))495 }496 Suffix::SuffixSlice(d) => {497 p!(o: {d.slice_desc()})498 }499 Suffix::SuffixApply(a) => {500 p!(o: {a.args_desc()})501 }502 }503 o504 }505}506impl Printable for Stmt {507 fn print(&self) -> PrintItems {508 match self {509 Stmt::StmtLocal(l) => {510 let mut pi = p!(new:);511 let (binds, end_comments) = children_between::<Bind>(512 l.syntax().clone(),513 l.local_kw_token().map(Into::into).as_ref(),514 l.semi_token().map(Into::into).as_ref(),515 None,516 );517 if binds.len() == 1 {518 let bind = &binds[0];519 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));520 p!(pi: str("local ") {bind.value});521 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?522 } else {523 p!(pi: str("local") >i nl);524 for bind in binds {525 if bind.should_start_with_newline {526 p!(pi: nl);527 }528 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));529 p!(pi: {bind.value} str(","));530 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);531 }532 if end_comments.should_start_with_newline {533 p!(pi: nl)534 }535 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));536 p!(pi: <i);537 }538 p!(pi: str(";") nl);539 pi540 }541 Stmt::StmtAssert(a) => {542 p!(new: {a.assertion()} str(";") nl)543 }544 }545 }546}547impl Printable for ExprBase {548 fn print(&self) -> PrintItems {549 match self {550 Self::ExprBinary(b) => {551 p!(new: {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})552 }553 Self::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),554 // Self::ExprSlice(s) => {555 // p!(new: {s.expr()} {s.slice_desc()})556 // }557 // Self::ExprIndex(i) => {558 // p!(new: {i.expr()} str(".") {i.index()})559 // }560 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),561 // Self::ExprApply(a) => {562 // let mut pi = p!(new: {a.expr()} {a.args_desc()});563 // if a.tailstrict_kw_token().is_some() {564 // p!(pi: str(" tailstrict"));565 // }566 // pi567 // }568 Self::ExprObjExtend(ex) => {569 p!(new: {ex.lhs_work()} str(" ") {ex.rhs_work()})570 }571 Self::ExprParened(p) => {572 p!(new: str("(") {p.expr()} str(")"))573 }574 Self::ExprString(s) => p!(new: {s.text()}),575 Self::ExprNumber(n) => p!(new: {n.number()}),576 Self::ExprArray(a) => {577 let mut pi = p!(new: str("[") >i nl);578 for el in a.exprs() {579 p!(pi: {el} str(",") nl);580 }581 p!(pi: <i str("]"));582 pi583 }584 Self::ExprObject(obj) => {585 p!(new: {obj.obj_body()})586 }587 Self::ExprArrayComp(arr) => {588 let mut pi = p!(new: str("[") {arr.expr()});589 for spec in arr.comp_specs() {590 p!(pi: str(" ") {spec});591 }592 p!(pi: str("]"));593 pi594 }595 Self::ExprImport(v) => {596 p!(new: {v.import_kind()} str(" ") {v.text()})597 }598 Self::ExprVar(n) => p!(new: {n.name()}),599 // Self::ExprLocal(l) => {600 // }601 Self::ExprIfThenElse(ite) => {602 let mut pi =603 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});604 if ite.else_kw_token().is_some() || ite.else_().is_some() {605 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})606 }607 pi608 }609 Self::ExprFunction(f) => p!(new: str("function") {f.params_desc()} nl {f.expr()}),610 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),611 Self::ExprError(e) => p!(new: str("error ") {e.expr()}),612 Self::ExprLiteral(l) => {613 p!(new: {l.literal()})614 }615 }616 }617}618619impl Printable for SourceFile {620 fn print(&self) -> PrintItems {621 let mut pi = p!(new:);622 let before = trivia_before(623 self.syntax().clone(),624 self.expr()625 .map(|e| e.syntax().clone())626 .map(Into::into)627 .as_ref(),628 );629 let after = trivia_after(630 self.syntax().clone(),631 self.expr()632 .map(|e| e.syntax().clone())633 .map(Into::into)634 .as_ref(),635 );636 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));637 p!(pi: {self.expr()} nl);638 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));639 pi640 }641}642643struct FormatOptions {644 // 0 for hard tabs645 indent: u8,646}647fn format(input: &str, opts: &FormatOptions) -> Option<String> {648 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);649 if !errors.is_empty() {650 let mut builder = ass_stroke::SnippetBuilder::new(input);651 for error in errors {652 builder653 .error(ass_stroke::Text::single(654 format!("{:?}", error.error).chars(),655 Default::default(),656 ))657 .range(658 error.range.start().into()659 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),660 )661 .build();662 }663 let snippet = builder.build();664 let ansi = ass_stroke::source_to_ansi(&snippet);665 eprintln!("{ansi}");666 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip667 // ERROR rowan nodes.668 // Recovery needs to be enabled for LSP, though.669 //670 // TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.671 return None;672 }673 Some(dprint_core::formatting::format(674 || parsed.print(),675 PrintOptions {676 indent_width: if opts.indent == 0 {677 // Reasonable max length for both 2 and 4 space sized tabs.678 3679 } else {680 opts.indent681 },682 max_width: 100,683 use_tabs: opts.indent == 0,684 new_line_text: "\n",685 },686 ))687}688689#[derive(Parser)]690struct Opts {691 /// Treat input as code, reformat it instead of reading file.692 #[clap(long, short = 'e')]693 exec: bool,694 /// Path to be reformatted if `--exec` if unset, otherwise code itself.695 input: String,696 /// Replace code with formatted in-place, instead of printing it to stdout.697 /// Only applicable if `--exec` is unset.698 #[clap(long, short = 'i')]699 in_place: bool,700701 /// Exit with error if formatted does not match input702 #[arg(long)]703 test: bool,704 /// Number of spaces to indent with705 ///706 /// 0 for guess from input (default), and use hard tabs if unable to guess.707 #[arg(long, default_value = "0")]708 indent: u8,709 /// Force hard tab for indentation710 #[arg(long)]711 hard_tabs: bool,712713 /// Debug option: how many times to call reformatting in case of unstable dprint output resolution.714 ///715 /// 0 for not retrying to reformat.716 #[arg(long, default_value = "0")]717 conv_limit: usize,718}719720#[derive(thiserror::Error, Debug)]721enum Error {722 #[error("--in-place is incompatible with --exec")]723 InPlaceExec,724 #[error("io: {0}")]725 Io(#[from] io::Error),726 #[error("persist: {0}")]727 Persist(#[from] tempfile::PersistError),728 #[error("parsing failed, refusing to reformat corrupted input")]729 ParseError,730}731732fn main_result() -> Result<(), Error> {733 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");734 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");735 let mut opts = Opts::parse();736 let input = if opts.exec {737 if opts.in_place {738 return Err(Error::InPlaceExec);739 }740 opts.input.clone()741 } else {742 fs::read_to_string(&opts.input)?743 };744745 if opts.indent == 0 {746 // Sane default.747 // TODO: Implement actual guessing.748 opts.hard_tabs = true;749 }750751 let mut iteration = 0;752 let mut formatted = input.clone();753 let mut tmp;754 // https://github.com/dprint/dprint/pull/423755 loop {756 let Some(reformatted) = format(757 &formatted,758 &FormatOptions {759 indent: if opts.indent == 0 || opts.hard_tabs {760 0761 } else {762 opts.indent763 },764 },765 ) else {766 return Err(Error::ParseError);767 };768 tmp = reformatted.trim().to_owned();769 if formatted == tmp {770 break;771 }772 formatted = tmp;773 if opts.conv_limit == 0 {774 break;775 }776 iteration += 1;777 if iteration > opts.conv_limit {778 panic!("formatting not converged");779 }780 }781 formatted.push('\n');782 if opts.test && formatted != input {783 process::exit(1);784 }785 if opts.in_place {786 let path = PathBuf::from(opts.input);787 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(788 "not failed during read, this path is not a directory, and there is a parent",789 ))?;790 temp.write_all(formatted.as_bytes())?;791 temp.flush()?;792 temp.persist(&path)?;793 } else {794 print!("{formatted}")795 }796 Ok(())797}798799fn main() {800 if let Err(e) = main_result() {801 eprintln!("{e}");802 process::exit(1);803 }804}1use std::{2 any::type_name,3 fs,4 io::{self, Write},5 path::PathBuf,6 process,7};89use children::{children_between, trivia_before};10use clap::Parser;11use dprint_core::formatting::{PrintItems, PrintOptions};12use jrsonnet_rowan_parser::{13 nodes::{14 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,17 UnaryOperator, Visibility,18 },19 AstNode, AstToken, SyntaxToken,20};2122use crate::{23 children::{trivia_after, trivia_between},24 comments::{format_comments, CommentLocation},25};2627mod children;28mod comments;29#[cfg(test)]30mod tests;3132pub trait Printable {33 fn print(&self) -> PrintItems;34}3536macro_rules! pi {37 (@i; $($t:tt)*) => {{38 #[allow(unused_mut)]39 let mut o = dprint_core::formatting::PrintItems::new();40 pi!(@s; o: $($t)*);41 o42 }};43 (@s; $o:ident: str($e:expr $(,)?) $($t:tt)*) => {{44 $o.push_str($e);45 pi!(@s; $o: $($t)*);46 }};47 (@s; $o:ident: string($e:expr $(,)?) $($t:tt)*) => {{48 $o.push_string($e);49 pi!(@s; $o: $($t)*);50 }};51 (@s; $o:ident: nl $($t:tt)*) => {{52 $o.push_signal(dprint_core::formatting::Signal::NewLine);53 pi!(@s; $o: $($t)*);54 }};55 (@s; $o:ident: tab $($t:tt)*) => {{56 $o.push_signal(dprint_core::formatting::Signal::Tab);57 pi!(@s; $o: $($t)*);58 }};59 (@s; $o:ident: >i $($t:tt)*) => {{60 $o.push_signal(dprint_core::formatting::Signal::StartIndent);61 pi!(@s; $o: $($t)*);62 }};63 (@s; $o:ident: <i $($t:tt)*) => {{64 $o.push_signal(dprint_core::formatting::Signal::FinishIndent);65 pi!(@s; $o: $($t)*);66 }};67 (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{68 $o.extend($expr.print());69 pi!(@s; $o: $($t)*);70 }};71 (@s; $o:ident: items($expr:expr) $($t:tt)*) => {{72 $o.extend($expr);73 pi!(@s; $o: $($t)*);74 }};75 (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{76 if $e {77 pi!(@s; $o: $($then)*);78 }79 pi!(@s; $o: $($t)*);80 }};81 (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{82 if $e {83 pi!(@s; $o: $($then)*);84 } else {85 pi!(@s; $o: $($else)*);86 }87 pi!(@s; $o: $($t)*);88 }};89 (@s; $i:ident:) => {}90}91macro_rules! p {92 (new: $($t:tt)*) => {93 pi!(@i; $($t)*)94 };95 ($o:ident: $($t:tt)*) => {96 pi!(@s; $o: $($t)*)97 };98}99pub(crate) use p;100pub(crate) use pi;101102impl<P> Printable for Option<P>103where104 P: Printable,105{106 fn print(&self) -> PrintItems {107 if let Some(v) = self {108 v.print()109 } else {110 p!(new: string(111 format!(112 "/*missing {}*/",113 type_name::<P>().replace("jrsonnet_rowan_parser::generated::nodes::", "")114 ),115 ))116 }117 }118}119120impl Printable for SyntaxToken {121 fn print(&self) -> PrintItems {122 p!(new: string(self.to_string()))123 }124}125126impl Printable for Text {127 fn print(&self) -> PrintItems {128 p!(new: string(format!("{}", self)))129 }130}131impl Printable for Number {132 fn print(&self) -> PrintItems {133 p!(new: string(format!("{}", self)))134 }135}136137impl Printable for Name {138 fn print(&self) -> PrintItems {139 p!(new: {self.ident_lit()})140 }141}142143impl Printable for DestructRest {144 fn print(&self) -> PrintItems {145 let mut pi = p!(new: str("..."));146 if let Some(name) = self.into() {147 p!(pi: {name});148 }149 pi150 }151}152153impl Printable for Destruct {154 fn print(&self) -> PrintItems {155 let mut pi = p!(new:);156 match self {157 Destruct::DestructFull(f) => {158 p!(pi: {f.name()})159 }160 Destruct::DestructSkip(_) => p!(pi: str("?")),161 Destruct::DestructArray(a) => {162 p!(pi: str("[") >i nl);163 for el in a.destruct_array_parts() {164 match el {165 DestructArrayPart::DestructArrayElement(e) => {166 p!(pi: {e.destruct()} str(",") nl)167 }168 DestructArrayPart::DestructRest(d) => {169 p!(pi: {d} str(",") nl)170 }171 }172 }173 p!(pi: <i str("]"));174 }175 Destruct::DestructObject(o) => {176 p!(pi: str("{") >i nl);177 for item in o.destruct_object_fields() {178 p!(pi: {item.field()});179 if let Some(des) = item.destruct() {180 p!(pi: str(": ") {des})181 }182 if let Some(def) = item.expr() {183 p!(pi: str(" = ") {def});184 }185 p!(pi: str(",") nl);186 }187 if let Some(rest) = o.destruct_rest() {188 p!(pi: {rest} nl)189 }190 p!(pi: <i str("}"));191 }192 }193 pi194 }195}196197impl Printable for FieldName {198 fn print(&self) -> PrintItems {199 match self {200 FieldName::FieldNameFixed(f) => {201 if let Some(id) = f.id() {202 p!(new: {id})203 } else if let Some(str) = f.text() {204 p!(new: {str})205 } else {206 p!(new: str("/*missing FieldName*/"))207 }208 }209 FieldName::FieldNameDynamic(d) => {210 p!(new: str("[") {d.expr()} str("]"))211 }212 }213 }214}215216impl Printable for Visibility {217 fn print(&self) -> PrintItems {218 p!(new: string(self.to_string()))219 }220}221222impl Printable for ObjLocal {223 fn print(&self) -> PrintItems {224 p!(new: str("local ") {self.bind()})225 }226}227228impl Printable for Assertion {229 fn print(&self) -> PrintItems {230 let mut pi = p!(new: str("assert ") {self.condition()});231 if self.colon_token().is_some() || self.message().is_some() {232 p!(pi: str(": ") {self.message()})233 }234 pi235 }236}237238impl Printable for ParamsDesc {239 fn print(&self) -> PrintItems {240 let mut pi = p!(new: str("(") >i nl);241 for param in self.params() {242 p!(pi: {param.destruct()});243 if param.assign_token().is_some() || param.expr().is_some() {244 p!(pi: str(" = ") {param.expr()})245 }246 p!(pi: str(",") nl)247 }248 p!(pi: <i str(")"));249 pi250 }251}252impl Printable for ArgsDesc {253 fn print(&self) -> PrintItems {254 let mut pi = p!(new: str("(") >i nl);255 for arg in self.args() {256 if arg.name().is_some() || arg.assign_token().is_some() {257 p!(pi: {arg.name()} str(" = "));258 }259 p!(pi: {arg.expr()} str(",") nl)260 }261 p!(pi: <i str(")"));262 pi263 }264}265impl Printable for SliceDesc {266 fn print(&self) -> PrintItems {267 let mut pi = p!(new: str("["));268 if self.from().is_some() {269 p!(pi: {self.from()});270 }271 p!(pi: str(":"));272 if self.end().is_some() {273 p!(pi: {self.end().map(|e|e.expr())})274 }275 // Keep only one : in case if we don't need step276 if self.step().is_some() {277 p!(pi: str(":") {self.step().map(|e|e.expr())});278 }279 p!(pi: str("]"));280 pi281 }282}283284impl Printable for Member {285 fn print(&self) -> PrintItems {286 match self {287 Member::MemberBindStmt(b) => {288 p!(new: {b.obj_local()})289 }290 Member::MemberAssertStmt(ass) => {291 p!(new: {ass.assertion()})292 }293 Member::MemberFieldNormal(n) => {294 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})295 }296 Member::MemberFieldMethod(m) => {297 p!(new: {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})298 }299 }300 }301}302303impl Printable for ObjBody {304 fn print(&self) -> PrintItems {305 match self {306 ObjBody::ObjBodyComp(l) => {307 let (children, mut end_comments) = children_between::<Member>(308 l.syntax().clone(),309 l.l_brace_token().map(Into::into).as_ref(),310 Some(311 &(l.comp_specs()312 .next()313 .expect("at least one spec is defined")314 .syntax()315 .clone())316 .into(),317 ),318 None,319 );320 let trailing_for_comp = end_comments.extract_trailing();321 let mut pi = p!(new: str("{") >i nl);322 for mem in children.into_iter() {323 if mem.should_start_with_newline {324 p!(pi: nl);325 }326 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));327 p!(pi: {mem.value} str(","));328 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));329 p!(pi: nl)330 }331332 if end_comments.should_start_with_newline {333 p!(pi: nl);334 }335 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));336337 let (compspecs, end_comments) = children_between::<CompSpec>(338 l.syntax().clone(),339 l.member_comps()340 .last()341 .map(|m| m.syntax().clone())342 .map(Into::into)343 .or_else(|| l.l_brace_token().map(Into::into))344 .as_ref(),345 l.r_brace_token().map(Into::into).as_ref(),346 Some(trailing_for_comp),347 );348 for mem in compspecs.into_iter() {349 if mem.should_start_with_newline {350 p!(pi: nl);351 }352 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));353 p!(pi: {mem.value});354 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));355 }356 if end_comments.should_start_with_newline {357 p!(pi: nl);358 }359 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));360361 p!(pi: nl <i str("}"));362 pi363 }364 ObjBody::ObjBodyMemberList(l) => {365 let (children, end_comments) = children_between::<Member>(366 l.syntax().clone(),367 l.l_brace_token().map(Into::into).as_ref(),368 l.r_brace_token().map(Into::into).as_ref(),369 None,370 );371 if children.is_empty() && end_comments.is_empty() {372 return p!(new: str("{ }"));373 }374 let mut pi = p!(new: str("{") >i nl);375 for (i, mem) in children.into_iter().enumerate() {376 if mem.should_start_with_newline && i != 0 {377 p!(pi: nl);378 }379 p!(pi: items(format_comments(&mem.before_trivia, CommentLocation::AboveItem)));380 p!(pi: {mem.value} str(","));381 p!(pi: items(format_comments(&mem.inline_trivia, CommentLocation::ItemInline)));382 p!(pi: nl)383 }384385 if end_comments.should_start_with_newline {386 p!(pi: nl);387 }388 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));389 p!(pi: <i str("}"));390 pi391 }392 }393 }394}395impl Printable for UnaryOperator {396 fn print(&self) -> PrintItems {397 p!(new: string(self.text().to_string()))398 }399}400impl Printable for BinaryOperator {401 fn print(&self) -> PrintItems {402 p!(new: string(self.text().to_string()))403 }404}405impl Printable for Bind {406 fn print(&self) -> PrintItems {407 match self {408 Bind::BindDestruct(d) => {409 p!(new: {d.into()} str(" = ") {d.value()})410 }411 Bind::BindFunction(f) => {412 p!(new: {f.name()} {f.params()} str(" = ") {f.value()})413 }414 }415 }416}417impl Printable for Literal {418 fn print(&self) -> PrintItems {419 p!(new: string(self.syntax().to_string()))420 }421}422impl Printable for ImportKind {423 fn print(&self) -> PrintItems {424 p!(new: string(self.syntax().to_string()))425 }426}427impl Printable for ForSpec {428 fn print(&self) -> PrintItems {429 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})430 }431}432impl Printable for IfSpec {433 fn print(&self) -> PrintItems {434 p!(new: str("if ") {self.expr()})435 }436}437impl Printable for CompSpec {438 fn print(&self) -> PrintItems {439 match self {440 CompSpec::ForSpec(f) => f.print(),441 CompSpec::IfSpec(i) => i.print(),442 }443 }444}445impl Printable for Expr {446 fn print(&self) -> PrintItems {447 let mut o = p!(new:);448 let (stmts, ending) = children_between::<Stmt>(449 self.syntax().clone(),450 None,451 self.expr_base()452 .as_ref()453 .map(ExprBase::syntax)454 .cloned()455 .map(Into::into)456 .as_ref(),457 None,458 );459 for stmt in stmts {460 p!(o: {stmt.value});461 }462 p!(o: {self.expr_base()});463 let (suffixes, ending) = children_between::<Suffix>(464 self.syntax().clone(),465 self.expr_base()466 .as_ref()467 .map(ExprBase::syntax)468 .cloned()469 .map(Into::into)470 .as_ref(),471 None,472 None,473 );474 for suffix in suffixes {475 p!(o: {suffix.value});476 }477 o478 }479}480impl Printable for Suffix {481 fn print(&self) -> PrintItems {482 let mut o = p!(new:);483 match self {484 Suffix::SuffixIndex(i) => {485 if i.question_mark_token().is_some() {486 p!(o: str("?"));487 }488 p!(o: str(".") {i.index()});489 }490 Suffix::SuffixIndexExpr(e) => {491 if e.question_mark_token().is_some() {492 p!(o: str(".?"));493 }494 p!(o: str("[") {e.index()} str("]"))495 }496 Suffix::SuffixSlice(d) => {497 p!(o: {d.slice_desc()})498 }499 Suffix::SuffixApply(a) => {500 p!(o: {a.args_desc()})501 }502 }503 o504 }505}506impl Printable for Stmt {507 fn print(&self) -> PrintItems {508 match self {509 Stmt::StmtLocal(l) => {510 let mut pi = p!(new:);511 let (binds, end_comments) = children_between::<Bind>(512 l.syntax().clone(),513 l.local_kw_token().map(Into::into).as_ref(),514 l.semi_token().map(Into::into).as_ref(),515 None,516 );517 if binds.len() == 1 {518 let bind = &binds[0];519 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));520 p!(pi: str("local ") {bind.value});521 // TODO: keep end_comments, child.inline_trivia somehow, force multiple locals formatting in case of presence?522 } else {523 p!(pi: str("local") >i nl);524 for bind in binds {525 if bind.should_start_with_newline {526 p!(pi: nl);527 }528 p!(pi: items(format_comments(&bind.before_trivia, CommentLocation::AboveItem)));529 p!(pi: {bind.value} str(","));530 p!(pi: items(format_comments(&bind.inline_trivia, CommentLocation::ItemInline)) nl);531 }532 if end_comments.should_start_with_newline {533 p!(pi: nl)534 }535 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));536 p!(pi: <i);537 }538 p!(pi: str(";") nl);539 pi540 }541 Stmt::StmtAssert(a) => {542 p!(new: {a.assertion()} str(";") nl)543 }544 }545 }546}547impl Printable for ExprBase {548 fn print(&self) -> PrintItems {549 match self {550 Self::ExprBinary(b) => {551 p!(new: {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})552 }553 Self::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),554 // Self::ExprSlice(s) => {555 // p!(new: {s.expr()} {s.slice_desc()})556 // }557 // Self::ExprIndex(i) => {558 // p!(new: {i.expr()} str(".") {i.index()})559 // }560 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),561 // Self::ExprApply(a) => {562 // let mut pi = p!(new: {a.expr()} {a.args_desc()});563 // if a.tailstrict_kw_token().is_some() {564 // p!(pi: str(" tailstrict"));565 // }566 // pi567 // }568 Self::ExprObjExtend(ex) => {569 p!(new: {ex.lhs_work()} str(" ") {ex.rhs_work()})570 }571 Self::ExprParened(p) => {572 p!(new: str("(") {p.expr()} str(")"))573 }574 Self::ExprString(s) => p!(new: {s.text()}),575 Self::ExprNumber(n) => p!(new: {n.number()}),576 Self::ExprArray(a) => {577 let mut pi = p!(new: str("[") >i nl);578 for el in a.exprs() {579 p!(pi: {el} str(",") nl);580 }581 p!(pi: <i str("]"));582 pi583 }584 Self::ExprObject(obj) => {585 p!(new: {obj.obj_body()})586 }587 Self::ExprArrayComp(arr) => {588 let mut pi = p!(new: str("[") {arr.expr()});589 for spec in arr.comp_specs() {590 p!(pi: str(" ") {spec});591 }592 p!(pi: str("]"));593 pi594 }595 Self::ExprImport(v) => {596 p!(new: {v.import_kind()} str(" ") {v.text()})597 }598 Self::ExprVar(n) => p!(new: {n.name()}),599 // Self::ExprLocal(l) => {600 // }601 Self::ExprIfThenElse(ite) => {602 let mut pi =603 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});604 if ite.else_kw_token().is_some() || ite.else_().is_some() {605 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})606 }607 pi608 }609 Self::ExprFunction(f) => p!(new: str("function") {f.params_desc()} nl {f.expr()}),610 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),611 Self::ExprError(e) => p!(new: str("error ") {e.expr()}),612 Self::ExprLiteral(l) => {613 p!(new: {l.literal()})614 }615 }616 }617}618619impl Printable for SourceFile {620 fn print(&self) -> PrintItems {621 let mut pi = p!(new:);622 let before = trivia_before(623 self.syntax().clone(),624 self.expr()625 .map(|e| e.syntax().clone())626 .map(Into::into)627 .as_ref(),628 );629 let after = trivia_after(630 self.syntax().clone(),631 self.expr()632 .map(|e| e.syntax().clone())633 .map(Into::into)634 .as_ref(),635 );636 p!(pi: items(format_comments(&before, CommentLocation::AboveItem)));637 p!(pi: {self.expr()} nl);638 p!(pi: items(format_comments(&after, CommentLocation::EndOfItems)));639 pi640 }641}642643struct FormatOptions {644 // 0 for hard tabs645 indent: u8,646}647fn format(input: &str, opts: &FormatOptions) -> Option<String> {648 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);649 if !errors.is_empty() {650 let mut builder = ass_stroke::SnippetBuilder::new(input);651 for error in errors {652 builder653 .error(ass_stroke::Text::single(654 format!("{:?}", error.error).chars(),655 Default::default(),656 ))657 .range(658 error.range.start().into()659 ..=(usize::from(error.range.end()) - 1).max(error.range.start().into()),660 )661 .build();662 }663 let snippet = builder.build();664 let ansi = ass_stroke::source_to_ansi(&snippet);665 eprintln!("{ansi}");666 // It is possible to recover from this failure, but the output may be broken, as formatter is free to skip667 // ERROR rowan nodes.668 // Recovery needs to be enabled for LSP, though.669 //670 // TODO: Verify how formatter interacts in cases of missing positional values, i.e `if cond then /*missing Expr*/ else residual`.671 return None;672 }673 Some(dprint_core::formatting::format(674 || parsed.print(),675 PrintOptions {676 indent_width: if opts.indent == 0 {677 // Reasonable max length for both 2 and 4 space sized tabs.678 3679 } else {680 opts.indent681 },682 max_width: 100,683 use_tabs: opts.indent == 0,684 new_line_text: "\n",685 },686 ))687}688689#[derive(Parser)]690struct Opts {691 /// Treat input as code, reformat it instead of reading file.692 #[clap(long, short = 'e')]693 exec: bool,694 /// Path to be reformatted if `--exec` if unset, otherwise code itself.695 input: String,696 /// Replace code with formatted in-place, instead of printing it to stdout.697 /// Only applicable if `--exec` is unset.698 #[clap(long, short = 'i')]699 in_place: bool,700701 /// Exit with error if formatted does not match input702 #[arg(long)]703 test: bool,704 /// Number of spaces to indent with705 ///706 /// 0 for guess from input (default), and use hard tabs if unable to guess.707 #[arg(long, default_value = "0")]708 indent: u8,709 /// Force hard tab for indentation710 #[arg(long)]711 hard_tabs: bool,712713 /// Debug option: how many times to call reformatting in case of unstable dprint output resolution.714 ///715 /// 0 for not retrying to reformat.716 #[arg(long, default_value = "0")]717 conv_limit: usize,718}719720#[derive(thiserror::Error, Debug)]721enum Error {722 #[error("--in-place is incompatible with --exec")]723 InPlaceExec,724 #[error("io: {0}")]725 Io(#[from] io::Error),726 #[error("persist: {0}")]727 Persist(#[from] tempfile::PersistError),728 #[error("parsing failed, refusing to reformat corrupted input")]729 ParseError,730}731732fn main_result() -> Result<(), Error> {733 eprintln!("jrsonnet-fmt is a prototype of a jsonnet code formatter, do not expect it to produce meaningful results right now.");734 eprintln!("It is not expected for its output to match other implementations, it will be completly separate implementation with maybe different name.");735 let mut opts = Opts::parse();736 let input = if opts.exec {737 if opts.in_place {738 return Err(Error::InPlaceExec);739 }740 opts.input.clone()741 } else {742 fs::read_to_string(&opts.input)?743 };744745 if opts.indent == 0 {746 // Sane default.747 // TODO: Implement actual guessing.748 opts.hard_tabs = true;749 }750751 let mut iteration = 0;752 let mut formatted = input.clone();753 let mut tmp;754 // https://github.com/dprint/dprint/pull/423755 loop {756 let Some(reformatted) = format(757 &formatted,758 &FormatOptions {759 indent: if opts.indent == 0 || opts.hard_tabs {760 0761 } else {762 opts.indent763 },764 },765 ) else {766 return Err(Error::ParseError);767 };768 tmp = reformatted.trim().to_owned();769 if formatted == tmp {770 break;771 }772 formatted = tmp;773 if opts.conv_limit == 0 {774 break;775 }776 iteration += 1;777 if iteration > opts.conv_limit {778 panic!("formatting not converged");779 }780 }781 formatted.push('\n');782 if opts.test && formatted != input {783 process::exit(1);784 }785 if opts.in_place {786 let path = PathBuf::from(opts.input);787 let mut temp = tempfile::NamedTempFile::new_in(path.parent().expect(788 "not failed during read, this path is not a directory, and there is a parent",789 ))?;790 temp.write_all(formatted.as_bytes())?;791 temp.flush()?;792 temp.persist(&path)?;793 } else {794 print!("{formatted}")795 }796 Ok(())797}798799fn main() {800 if let Err(e) = main_result() {801 eprintln!("{e}");802 process::exit(1);803 }804}