difftreelog
refactor use new rowan-parser in jrsonnet-fmt
in: master
6 files changed
Cargo.lockdiffbeforeafterbothno changes
cmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth4edition = "2021"4edition = "2021"556[dependencies]6[dependencies]7dprint-core = "0.60.0"7dprint-core = "0.63.2"8jrsonnet-rowan-parser.workspace = true8jrsonnet-rowan-parser.workspace = true9insta = "1.15"9insta = "1.15"10indoc = "1.0"10indoc = "1.0"11ass-stroke = { path = "/home/lach/build/ass-stroke/crates/ass-stroke", version = "0.1.0" }11ass-stroke = { path = "/home/lach/build/ass-stroke/crates/ass-stroke", version = "0.1.0" }12clap = { version = "4.4.2", features = ["derive"] }13tempfile = "3.8.0"14thiserror = "1.0.48"1215cmds/jrsonnet-fmt/src/children.rsdiffbeforeafterboth99 node: SyntaxNode,99 node: SyntaxNode,100 start: Option<&SyntaxElement>,100 start: Option<&SyntaxElement>,101 end: Option<&SyntaxElement>,101 end: Option<&SyntaxElement>,102 trailing: Option<ChildTrivia>,102) -> (Vec<Child<T>>, EndingComments) {103) -> (Vec<Child<T>>, EndingComments) {103 let mut iter = node.children_with_tokens().peekable();104 let mut iter = node.children_with_tokens().peekable();105 if start.is_some() {104 while iter.peek() != start {106 while iter.peek() != start {105 iter.next();107 iter.next();106 }108 }107 iter.next();109 iter.next();110 }108 children(111 children(109 iter.take_while(|i| Some(i) != end),112 iter.take_while(|i| Some(i) != end),110 start.is_none() || end.is_none(),113 start.is_none() && end.is_none(),114 trailing,111 )115 )112}116}113117165pub fn children<T: AstNode + Debug>(169pub fn children<T: AstNode + Debug>(166 items: impl Iterator<Item = SyntaxElement>,170 items: impl Iterator<Item = SyntaxElement>,167 loose: bool,171 loose: bool,172 mut trailing: Option<ChildTrivia>,168) -> (Vec<Child<T>>, EndingComments) {173) -> (Vec<Child<T>>, EndingComments) {169 let mut out = Vec::new();174 let mut out = Vec::new();170 let mut current_child = None::<Child<T>>;175 let mut current_child = None::<Child<T>>;175180176 for item in items {181 for item in items {177 if let Some(value) = item.as_node().cloned().and_then(T::cast) {182 if let Some(value) = item.as_node().cloned().and_then(T::cast) {178 let before_trivia = mem::take(&mut next);183 let before_trivia = if let Some(trailing) = trailing.take() {184 assert!(next.is_empty());185 trailing186 } else {187 mem::take(&mut next)188 };179 let last_child = current_child.replace(Child {189 let last_child = current_child.replace(Child {180 // First item should not start with newline190 // First item should not start with newline181 should_start_with_newline: had_some191 should_start_with_newline: had_some195 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {205 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {196 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment206 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment197 || trivia.kind() == TriviaKind::SingleLineSlashComment;207 || trivia.kind() == TriviaKind::SingleLineSlashComment;208 if trailing.is_some() {209 // Someone have already parsed trivia for us210 continue;198 if started_next211 } else if started_next199 || current_child.is_none()212 || current_child.is_none()200 || trivia.text().contains('\n') && !is_single_line_comment213 || trivia.text().contains('\n') && !is_single_line_comment201 {214 {271 pub fn is_empty(&self) -> bool {284 pub fn is_empty(&self) -> bool {272 !self.should_start_with_newline && self.trivia.is_empty()285 !self.should_start_with_newline && self.trivia.is_empty()273 }286 }287 pub fn extract_trailing(&mut self) -> ChildTrivia {288 mem::take(&mut self.trivia)289 }274}290}275291cmds/jrsonnet-fmt/src/comments.rsdiffbeforeafterboth28 match text.as_bytes()[0] {28 match text.as_bytes()[0] {29 b'\n' => p!(pi: nl),29 b'\n' => p!(pi: nl),30 b'\t' => p!(pi: tab),30 b'\t' => p!(pi: tab),31 _ => unreachable!()31 _ => unreachable!(),32 }32 }33 text = &text[1..];33 text = &text[1..];34 }34 }69 lines.pop();69 lines.pop();70 }70 }71 if lines.len() == 1 && !doc {71 if lines.len() == 1 && !doc {72 if matches!(loc, CommentLocation::ItemInline) {73 p!(pi: str(" "));74 }72 p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */") nl)75 p!(pi: str("/* ") string(lines[0].trim().to_string()) str(" */"))73 } else if !lines.is_empty() {76 } else if !lines.is_empty() {74 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {77 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {75 let offset = a78 let offset = acmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth1use std::any::type_name;1use std::{2 any::type_name,3 fs,4 io::{self, Write},5 path::PathBuf,6 process,7};283use children::{children_between, trivia_before};9use children::{children_between, trivia_before};10use clap::Parser;4use dprint_core::formatting::{PrintItems, PrintOptions};11use dprint_core::formatting::{PrintItems, PrintOptions};5use jrsonnet_rowan_parser::{12use jrsonnet_rowan_parser::{6 nodes::{13 nodes::{7 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,14 ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,8 DestructRest, Expr, FieldName, ForSpec, IfSpec, ImportKind, LhsExpr, Literal, Member, Name,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,9 Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Text, UnaryOperator,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,10 Visibility, VisibilityKind,17 UnaryOperator, Visibility,11 },18 },12 rowan::NodeOrToken,13 AstNode, AstToken, SyntaxToken,19 AstNode, AstToken, SyntaxToken,14};20};1521287 Member::MemberFieldNormal(n) => {293 Member::MemberFieldNormal(n) => {288 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})294 p!(new: {n.field_name()} if(n.plus_token().is_some())({n.plus_token()}) {n.visibility()} str(" ") {n.expr()})289 }295 }290 Member::MemberFieldMethod(_) => todo!(),296 Member::MemberFieldMethod(m) => {297 p!(new: {m.field_name()} {m.params_desc()} {m.visibility()} str(" ") {m.expr()})298 }291 }299 }292 }300 }293}301}296 fn print(&self) -> PrintItems {304 fn print(&self) -> PrintItems {297 match self {305 match self {298 ObjBody::ObjBodyComp(l) => {306 ObjBody::ObjBodyComp(l) => {299 let (children, end_comments) = children_between::<Member>(307 let (children, mut end_comments) = children_between::<Member>(300 l.syntax().clone(),308 l.syntax().clone(),301 l.l_brace_token().map(Into::into).as_ref(),309 l.l_brace_token().map(Into::into).as_ref(),302 Some(310 Some(307 .clone())315 .clone())308 .into(),316 .into(),309 ),317 ),318 None,310 );319 );320 let trailing_for_comp = end_comments.extract_trailing();311 let mut pi = p!(new: str("{") >i nl);321 let mut pi = p!(new: str("{") >i nl);312 for mem in children.into_iter() {322 for mem in children.into_iter() {313 if mem.should_start_with_newline {323 if mem.should_start_with_newline {333 .or_else(|| l.l_brace_token().map(Into::into))343 .or_else(|| l.l_brace_token().map(Into::into))334 .as_ref(),344 .as_ref(),335 l.r_brace_token().map(Into::into).as_ref(),345 l.r_brace_token().map(Into::into).as_ref(),346 Some(trailing_for_comp),336 );347 );337 for mem in compspecs.into_iter() {348 for mem in compspecs.into_iter() {338 if mem.should_start_with_newline {349 if mem.should_start_with_newline {347 }358 }348 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));359 p!(pi: items(format_comments(&end_comments.trivia, CommentLocation::EndOfItems)));349360350 p!(pi: <i str("}"));361 p!(pi: nl <i str("}"));351 pi362 pi352 }363 }353 ObjBody::ObjBodyMemberList(l) => {364 ObjBody::ObjBodyMemberList(l) => {354 let (children, end_comments) = children_between::<Member>(365 let (children, end_comments) = children_between::<Member>(355 l.syntax().clone(),366 l.syntax().clone(),356 l.l_brace_token().map(Into::into).as_ref(),367 l.l_brace_token().map(Into::into).as_ref(),357 l.r_brace_token().map(Into::into).as_ref(),368 l.r_brace_token().map(Into::into).as_ref(),369 None,358 );370 );359 if children.is_empty() && end_comments.is_empty() {371 if children.is_empty() && end_comments.is_empty() {360 return p!(new: str("{ }"));372 return p!(new: str("{ }"));412 p!(new: string(self.syntax().to_string()))424 p!(new: string(self.syntax().to_string()))413 }425 }414}426}415impl Printable for LhsExpr {416 fn print(&self) -> PrintItems {417 p!(new: {self.expr()})418 }419}420impl Printable for ForSpec {427impl Printable for ForSpec {421 fn print(&self) -> PrintItems {428 fn print(&self) -> PrintItems {422 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})429 p!(new: str("for ") {self.bind()} str(" in ") {self.expr()})435 }442 }436 }443 }437}444}438impl Printable for Expr {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 {439 fn print(&self) -> PrintItems {548 fn print(&self) -> PrintItems {440 match self {549 match self {441 Expr::ExprBinary(b) => {550 Self::ExprBinary(b) => {442 p!(new: {b.lhs()} str(" ") {b.binary_operator()} str(" ") {b.rhs()})551 p!(new: {b.lhs_work()} str(" ") {b.binary_operator()} str(" ") {b.rhs_work()})443 }552 }444 Expr::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),553 Self::ExprUnary(u) => p!(new: {u.unary_operator()} {u.rhs()}),445 Expr::ExprSlice(s) => {554 // Self::ExprSlice(s) => {446 p!(new: {s.expr()} {s.slice_desc()})555 // p!(new: {s.expr()} {s.slice_desc()})447 }556 // }448 Expr::ExprIndex(i) => {557 // Self::ExprIndex(i) => {449 p!(new: {i.expr()} str(".") {i.index()})558 // p!(new: {i.expr()} str(".") {i.index()})450 }559 // }451 Expr::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),560 // Self::ExprIndexExpr(i) => p!(new: {i.base()} str("[") {i.index()} str("]")),452 Expr::ExprApply(a) => {561 // Self::ExprApply(a) => {453 let mut pi = p!(new: {a.expr()} {a.args_desc()});562 // let mut pi = p!(new: {a.expr()} {a.args_desc()});454 if a.tailstrict_kw_token().is_some() {563 // if a.tailstrict_kw_token().is_some() {455 p!(pi: str(" tailstrict"));564 // p!(pi: str(" tailstrict"));456 }565 // }457 pi566 // pi458 }567 // }459 Expr::ExprObjExtend(ex) => {568 Self::ExprObjExtend(ex) => {460 p!(new: {ex.lhs_expr()} str(" ") {ex.expr()})569 p!(new: {ex.lhs_work()} str(" ") {ex.rhs_work()})461 }570 }462 Expr::ExprParened(p) => {571 Self::ExprParened(p) => {463 p!(new: str("(") {p.expr()} str(")"))572 p!(new: str("(") {p.expr()} str(")"))464 }573 }465 Expr::ExprString(s) => p!(new: {s.text()}),574 Self::ExprString(s) => p!(new: {s.text()}),466 Expr::ExprNumber(n) => p!(new: {n.number()}),575 Self::ExprNumber(n) => p!(new: {n.number()}),467 Expr::ExprArray(a) => {576 Self::ExprArray(a) => {468 let mut pi = p!(new: str("[") >i nl);577 let mut pi = p!(new: str("[") >i nl);469 for el in a.exprs() {578 for el in a.exprs() {470 p!(pi: {el} str(",") nl);579 p!(pi: {el} str(",") nl);471 }580 }472 p!(pi: <i str("]"));581 p!(pi: <i str("]"));473 pi582 pi474 }583 }475 Expr::ExprObject(o) => {584 Self::ExprObject(obj) => {476 p!(new: {o.obj_body()})585 p!(new: {obj.obj_body()})477 }586 }478 Expr::ExprArrayComp(arr) => {587 Self::ExprArrayComp(arr) => {479 let mut pi = p!(new: str("[") {arr.expr()});588 let mut pi = p!(new: str("[") {arr.expr()});480 for spec in arr.comp_specs() {589 for spec in arr.comp_specs() {481 p!(pi: str(" ") {spec});590 p!(pi: str(" ") {spec});482 }591 }483 p!(pi: str("]"));592 p!(pi: str("]"));484 pi593 pi485 }594 }486 Expr::ExprImport(v) => {595 Self::ExprImport(v) => {487 p!(new: {v.import_kind()} str(" ") {v.text()})596 p!(new: {v.import_kind()} str(" ") {v.text()})488 }597 }489 Expr::ExprVar(n) => p!(new: {n.name()}),598 Self::ExprVar(n) => p!(new: {n.name()}),490 Expr::ExprLocal(l) => {599 // Self::ExprLocal(l) => {491 let mut pi = p!(new:);600 // }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) => {601 Self::ExprIfThenElse(ite) => {537 let mut pi =602 let mut pi =538 p!(new: str("if ") {ite.cond()} str(" then ") {ite.then().map(|t| t.expr())});603 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() {604 if ite.else_kw_token().is_some() || ite.else_().is_some() {540 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})605 p!(pi: str(" else ") {ite.else_().map(|t| t.expr())})541 }606 }542 pi607 pi543 }608 }544 Expr::ExprFunction(f) => p!(new: str("function") {f.params_desc()} str(" ") {f.expr()}),609 Self::ExprFunction(f) => p!(new: str("function") {f.params_desc()} nl {f.expr()}),545 Expr::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),610 // Self::ExprAssert(a) => p!(new: {a.assertion()} str("; ") {a.expr()}),546 Expr::ExprError(e) => p!(new: str("error ") {e.expr()}),611 Self::ExprError(e) => p!(new: str("error ") {e.expr()}),547 Expr::ExprLiteral(l) => {612 Self::ExprLiteral(l) => {548 p!(new: {l.literal()})613 p!(new: {l.literal()})549 }614 }550 }615 }575 }640 }576}641}577642643struct FormatOptions {644 // 0 for hard tabs645 indent: u8,646}578fn format(input: &str) -> String {647fn format(input: &str, opts: &FormatOptions) -> Option<String> {579 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);648 let (parsed, errors) = jrsonnet_rowan_parser::parse(input);580 if !errors.is_empty() {649 if !errors.is_empty() {581 let mut builder = ass_stroke::SnippetBuilder::new(input);650 let mut builder = ass_stroke::SnippetBuilder::new(input);593 }662 }594 let snippet = builder.build();663 let snippet = builder.build();595 let ansi = ass_stroke::source_to_ansi(&snippet);664 let ansi = ass_stroke::source_to_ansi(&snippet);596 println!("{ansi}");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;597 }672 }598 dprint_core::formatting::format(673 Some(dprint_core::formatting::format(599 || parsed.print(),674 || parsed.print(),600 PrintOptions {675 PrintOptions {601 indent_width: 2,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 },602 max_width: 100,682 max_width: 100,603 use_tabs: false,683 use_tabs: opts.indent == 0,604 new_line_text: "\n",684 new_line_text: "\n",605 },685 },606 )686 ))607}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}731608fn main() {732fn 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();609 let input = r#"736 let input = if opts.exec {610737 if opts.in_place {611738 return Err(Error::InPlaceExec);612 # Edit me!739 }613 local b = import "b.libsonnet"; # comment740 opts.input.clone()614 local a = import "a.libsonnet";741 } else {615742 fs::read_to_string(&opts.input)?616 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"#;743 };744745 if opts.indent == 0 {746 // Sane default.747 // TODO: Implement actual guessing.748 opts.hard_tabs = true;749 }733750734 let mut iteration = 0;751 let mut iteration = 0;735 let mut a = input.to_string();752 let mut formatted = input.clone();736 let mut b;753 let mut tmp;737 // https://github.com/dprint/dprint/pull/423754 // https://github.com/dprint/dprint/pull/423738 loop {755 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 };739 b = format(&a).trim().to_owned();768 tmp = reformatted.trim().to_owned();740 if a == b {769 if formatted == tmp {741 break;770 break;742 }771 }743 println!("{b}");772 formatted = tmp;773 if opts.conv_limit == 0 {744 a = b;774 break;775 }745 iteration += 1;776 iteration += 1;746 if iteration > 5 {777 if iteration > opts.conv_limit {747 panic!("formatting not converged");778 panic!("formatting not converged");748 break;749 }779 }750 }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 {751 println!("{a}");794 print!("{formatted}")795 }796 Ok(())752}797}798799fn main() {800 if let Err(e) = main_result() {801 eprintln!("{e}");802 process::exit(1);803 }804}753805cmds/jrsonnet/src/main.rsdiffbeforeafterboth37#[derive(Parser)]37#[derive(Parser)]38#[clap(next_help_heading = "INPUT")]38#[clap(next_help_heading = "INPUT")]39struct InputOpts {39struct InputOpts {40 /// Treat input as code, evaluate them instead of reading file40 /// Treat input as code, evaluate it instead of reading file.41 #[clap(long, short = 'e')]41 #[clap(long, short = 'e')]42 pub exec: bool,42 pub exec: bool,434344 /// Path to the file to be compiled if `--evaluate` is unset, otherwise code itself44 /// Path to the file to be compiled if `--exec` is unset, otherwise code itself.45 pub input: Option<String>,45 pub input: Option<String>,464647 /// After executing input, apply specified code.47 /// After executing input, apply specified code.48 /// Output of the initial input will be accessible using `$`48 /// Output of the initial input will be accessible using `_`.49 #[cfg(feature = "exp-apply")]49 #[cfg(feature = "exp-apply")]50 #[clap(long)]50 #[clap(long)]51 pub exp_apply: Vec<String>,51 pub exp_apply: Vec<String>,