difftreelog
Merge pull request #146 from CertainLach/fix/tests
in: master
Fix failing CI for tests and lints
22 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}crates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/manifest.rs
+++ b/crates/jrsonnet-cli/src/manifest.rs
@@ -6,7 +6,7 @@
};
use jrsonnet_stdlib::{TomlFormat, YamlFormat};
-#[derive(Clone, ValueEnum)]
+#[derive(Clone, Copy, ValueEnum)]
pub enum ManifestFormatName {
/// Expect string as output, and write them directly
String,
@@ -18,9 +18,11 @@
#[derive(Parser)]
#[clap(next_help_heading = "MANIFESTIFICATION OUTPUT")]
pub struct ManifestOpts {
- /// Output format, wraps resulting value to corresponding std.manifest call.
- #[clap(long, short = 'f', default_value = "json")]
- format: ManifestFormatName,
+ /// Output format, wraps resulting value to corresponding std.manifest call
+ ///
+ /// [default: json, yaml when -y is used]
+ #[clap(long, short = 'f')]
+ format: Option<ManifestFormatName>,
/// Expect plain string as output.
/// Mutually exclusive with `--format`
#[clap(long, short = 'S', conflicts_with = "format")]
@@ -29,7 +31,9 @@
#[clap(long, short = 'y', conflicts_with = "string")]
yaml_stream: bool,
/// Number of spaces to pad output manifest with.
- /// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml/toml]
+ /// `0` for hard tabs, `-1` for single line output
+ ///
+ /// [default: 3 for json, 2 for yaml/toml]
#[clap(long)]
line_padding: Option<usize>,
/// Preserve order in object manifestification
@@ -44,7 +48,12 @@
} else {
#[cfg(feature = "exp-preserve-order")]
let preserve_order = self.preserve_order;
- match self.format {
+ let format = match self.format {
+ Some(v) => v,
+ None if self.yaml_stream => ManifestFormatName::Yaml,
+ None => ManifestFormatName::Json,
+ };
+ match format {
ManifestFormatName::String => Box::new(ToStringFormat),
ManifestFormatName::Json => Box::new(JsonFormat::cli(
self.line_padding.unwrap_or(3),
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -372,7 +372,7 @@
pub fn new_inclusive(start: i32, end: i32) -> Self {
Self { start, end }
}
- fn range(&self) -> impl Iterator<Item = i32> + ExactSizeIterator + DoubleEndedIterator {
+ fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {
WithExactSize(
self.start..=self.end,
(self.end as usize)
@@ -461,7 +461,7 @@
ArrayThunk::Waiting(..) => {}
};
- let ArrayThunk::Waiting(_) =
+ let ArrayThunk::Waiting(()) =
replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
else {
unreachable!()
@@ -508,7 +508,7 @@
match &self.cached.borrow()[index] {
ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
- ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
+ ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}
};
Some(Thunk::new(ArrayElement {
@@ -597,9 +597,7 @@
}
fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- let Some(key) = self.keys.get(index) else {
- return None;
- };
+ let key = self.keys.get(index)?;
Some(self.obj.get_lazy_or_bail(key.clone()))
}
@@ -649,9 +647,7 @@
}
fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- let Some(key) = self.keys.get(index) else {
- return None;
- };
+ let key = self.keys.get(index)?;
// Nothing can fail in the key part, yet value is still
// lazy-evaluated
Some(Thunk::evaluated(
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -89,7 +89,7 @@
specs: &[CompSpec],
callback: &mut impl FnMut(Context) -> Result<()>,
) -> Result<()> {
- match specs.get(0) {
+ match specs.first() {
None => callback(ctx)?,
Some(CompSpec::IfSpec(IfSpecData(cond))) => {
if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -6,8 +6,8 @@
use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
use crate::{gc::TraceBox, tb, Context, Result, Val};
-/// Can't have str | IStr, because constant BuiltinParam causes
-/// E0492: constant functions cannot refer to interior mutable data
+/// Can't have `str` | `IStr`, because constant `BuiltinParam` causes
+/// `E0492: constant functions cannot refer to interior mutable data`
#[derive(Clone, Trace)]
pub struct ParamName(Option<Cow<'static, str>>);
impl ParamName {
@@ -27,10 +27,9 @@
}
impl PartialEq<IStr> for ParamName {
fn eq(&self, other: &IStr) -> bool {
- match &self.0 {
- Some(s) => s.as_bytes() == other.as_bytes(),
- None => false,
- }
+ self.0
+ .as_ref()
+ .map_or(false, |s| s.as_bytes() == other.as_bytes())
}
}
@@ -87,7 +86,7 @@
params: params
.into_iter()
.map(|n| BuiltinParam {
- name: ParamName::new_dynamic(n.to_string()),
+ name: ParamName::new_dynamic(n),
has_default: false,
})
.collect(),
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -159,11 +159,11 @@
Val::Null => serializer.serialize_none(),
Val::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
Val::Num(n) => {
- if n.fract() != 0.0 {
- serializer.serialize_f64(*n)
- } else {
+ if n.fract() == 0.0 {
let n = *n as i64;
serializer.serialize_i64(n)
+ } else {
+ serializer.serialize_f64(*n)
}
}
#[cfg(feature = "exp-bigint")]
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -41,10 +41,12 @@
clippy::missing_const_for_fn,
// too many false-positives with .expect() calls
clippy::missing_panics_doc,
- // false positive for IStr type. There is an configuration option for
- // such cases, but it doesn't work:
- // https://github.com/rust-lang/rust-clippy/issues/9801
- clippy::mutable_key_type,
+ // false positive for IStr type. There is an configuration option for
+ // such cases, but it doesn't work:
+ // https://github.com/rust-lang/rust-clippy/issues/9801
+ clippy::mutable_key_type,
+ // false positives
+ clippy::redundant_pub_crate,
)]
// For jrsonnet-macros
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/manifest.rs
@@ -175,6 +175,8 @@
manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;
Ok(out)
}
+
+#[allow(clippy::too_many_lines)]
fn manifest_json_ex_buf(
val: &Val,
buf: &mut String,
crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -171,7 +171,7 @@
// .field("assertions_ran", &self.assertions_ran)
.field("this_entries", &self.this_entries)
// .field("value_cache", &self.value_cache)
- .finish()
+ .finish_non_exhaustive()
}
}
@@ -347,7 +347,7 @@
out.with_super(self);
let mut member = out.field(key);
if value.flags.add() {
- member = member.add()
+ member = member.add();
}
if let Some(loc) = value.location {
member = member.with_location(loc);
@@ -395,7 +395,7 @@
pub fn get(&self, key: IStr) -> Result<Option<Val>> {
self.run_assertions()?;
- self.get_for(key, self.0.this().unwrap_or(self.clone()))
+ self.get_for(key, self.0.this().unwrap_or_else(|| self.clone()))
}
pub fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
@@ -474,7 +474,7 @@
type Output = Val;
fn get(self: Box<Self>) -> Result<Self::Output> {
- Ok(self.obj.get_or_bail(self.key)?)
+ self.obj.get_or_bail(self.key)
}
}
@@ -495,7 +495,7 @@
SuperDepth::default(),
&mut |depth, index, name, visibility| {
let new_sort_key = FieldSortKey::new(depth, index);
- let entry = out.entry(name.clone());
+ let entry = out.entry(name);
let (visible, _) = entry.or_insert((true, new_sort_key));
match visibility {
Visibility::Normal => {}
@@ -634,7 +634,7 @@
SuperDepth::default(),
&mut |depth, index, name, visibility| {
let new_sort_key = FieldSortKey::new(depth, index);
- let entry = out.entry(name.clone());
+ let entry = out.entry(name);
let (visible, _) = entry.or_insert((true, new_sort_key));
match visibility {
Visibility::Normal => {}
crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -248,7 +248,7 @@
let (cflags, str) = try_parse_cflags(str)?;
let (width, str) = try_parse_field_width(str)?;
let (precision, str) = try_parse_precision(str)?;
- let (_, str) = try_parse_length_modifier(str)?;
+ let ((), str) = try_parse_length_modifier(str)?;
let (convtype, str) = parse_conversion_type(str)?;
Ok((
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -449,25 +449,21 @@
}
fn from_untyped(value: Val) -> Result<Self> {
- match &value {
- Val::Arr(a) => {
- if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {
- return Ok(bytes.0.as_slice().into());
- };
- <Self as Typed>::TYPE.check(&value)?;
- // Any::downcast_ref::<ByteArray>(&a);
- let mut out = Vec::with_capacity(a.len());
- for e in a.iter() {
- let r = e?;
- out.push(u8::from_untyped(r)?);
- }
- Ok(out.as_slice().into())
- }
- _ => {
- <Self as Typed>::TYPE.check(&value)?;
- unreachable!()
- }
+ let Val::Arr(a) = &value else {
+ <Self as Typed>::TYPE.check(&value)?;
+ unreachable!()
+ };
+ if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {
+ return Ok(bytes.0.as_slice().into());
+ };
+ <Self as Typed>::TYPE.check(&value)?;
+ // Any::downcast_ref::<ByteArray>(&a);
+ let mut out = Vec::with_capacity(a.len());
+ for e in a.iter() {
+ let r = e?;
+ out.push(u8::from_untyped(r)?);
}
+ Ok(out.as_slice().into())
}
}
crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -90,7 +90,7 @@
item: impl Fn() -> Result<()>,
) -> Result<()> {
State::push_description(error_reason, || match item() {
- Ok(_) => Ok(()),
+ Ok(()) => Ok(()),
Err(mut e) => {
if let ErrorKind::TypeError(e) = &mut e.error_mut() {
(e.1).0.push(path());
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -351,6 +351,8 @@
}
}
impl PartialEq for StrValue {
+ // False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.
+ #[allow(clippy::unconditional_recursion)]
fn eq(&self, other: &Self) -> bool {
let a = self.clone().into_flat();
let b = other.clone().into_flat();
crates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -6,7 +6,7 @@
#![warn(clippy::pedantic, clippy::nursery)]
#![allow(clippy::missing_const_for_fn)]
use std::{
- borrow::{Borrow, Cow},
+ borrow::Cow,
cell::RefCell,
fmt::{self, Display},
hash::{BuildHasherDefault, Hash, Hasher},
@@ -14,7 +14,7 @@
str,
};
-use hashbrown::HashMap;
+use hashbrown::{hash_map::RawEntryMut, HashMap};
use jrsonnet_gcmodule::Trace;
use rustc_hash::FxHasher;
@@ -57,17 +57,6 @@
}
}
-impl Borrow<str> for IStr {
- fn borrow(&self) -> &str {
- self.as_str()
- }
-}
-impl Borrow<[u8]> for IStr {
- fn borrow(&self) -> &[u8] {
- self.as_bytes()
- }
-}
-
impl PartialEq for IStr {
fn eq(&self, other: &Self) -> bool {
// all IStr should be inlined into same pool
@@ -142,12 +131,6 @@
type Target = [u8];
fn deref(&self) -> &Self::Target {
- self.0.as_slice()
- }
-}
-
-impl Borrow<[u8]> for IBytes {
- fn borrow(&self) -> &[u8] {
self.0.as_slice()
}
}
@@ -285,9 +268,9 @@
let mut pool = pool.borrow_mut();
let entry = pool.raw_entry_mut().from_key(bytes);
match entry {
- hashbrown::hash_map::RawEntryMut::Occupied(i) => IBytes(i.get_key_value().0.clone()),
- hashbrown::hash_map::RawEntryMut::Vacant(e) => {
- let (k, _) = e.insert(Inner::new_bytes(bytes), ());
+ RawEntryMut::Occupied(i) => IBytes(i.get_key_value().0.clone()),
+ RawEntryMut::Vacant(e) => {
+ let (k, ()) = e.insert(Inner::new_bytes(bytes), ());
IBytes(k.clone())
}
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -374,6 +374,7 @@
fn params(&self) -> &[BuiltinParam] {
PARAMS
}
+ #[allow(unused_variable)]
fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;
crates/jrsonnet-stdlib/src/encoding.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/encoding.rs
+++ b/crates/jrsonnet-stdlib/src/encoding.rs
@@ -39,5 +39,5 @@
let bytes = STANDARD
.decode(str.as_bytes())
.map_err(|e| runtime_error!("invalid base64: {e}"))?;
- Ok(String::from_utf8(bytes).map_err(|_| runtime_error!("bad utf8"))?)
+ String::from_utf8(bytes).map_err(|_| runtime_error!("bad utf8"))
}
crates/jrsonnet-stdlib/src/manifest/yaml.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/manifest/yaml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/yaml.rs
@@ -134,6 +134,14 @@
buf.push_str(&options.padding);
buf.push_str(line);
}
+ } else if s.contains('\n') {
+ buf.push_str("|-");
+ for line in s.split('\n') {
+ buf.push('\n');
+ buf.push_str(cur_padding);
+ buf.push_str(&options.padding);
+ buf.push_str(line);
+ }
} else if !options.quote_keys && !yaml_needs_quotes(&s) {
buf.push_str(&s);
} else {
crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -47,7 +47,7 @@
.ext_natives
.get(&x)
.cloned()
- .map_or(Val::Null, |v| Val::Func(v))
+ .map_or(Val::Null, Val::Func)
}
#[builtin(fields(
flake.lockdiffbeforeafterboth--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1694529238,
- "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+ "lastModified": 1705309234,
+ "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+ "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1701376520,
- "narHash": "sha256-U3iGiOZqgu7wvVzgfoQzGGFMqNsDj/q/6zPIjCy7ajg=",
+ "lastModified": 1705391267,
+ "narHash": "sha256-gGVm9QudiRtYTX8PN9cTTy7uuJcL4I2lRMoPx496kXk=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "c74cc3c3db2ed5e68895953d75c397797d499133",
+ "rev": "41a9a7f170c740acb24f3390323877d11c69d5ee",
"type": "github"
},
"original": {
@@ -50,11 +50,11 @@
]
},
"locked": {
- "lastModified": 1701310566,
- "narHash": "sha256-CL9J3xUR2Ejni4LysrEGX0IdO+Y4BXCiH/By0lmF3eQ=",
+ "lastModified": 1705371439,
+ "narHash": "sha256-P1kulUXpYWkcrjiX3sV4j8ACJZh9XXSaaD+jDLBDLKo=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "6d3c6e185198b8bf7ad639f22404a75aa9a09bff",
+ "rev": "b21f3c0d5bf0f0179f5f0140e8e0cd099618bd04",
"type": "github"
},
"original": {
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ b/flake.nix
@@ -25,14 +25,14 @@
lib = pkgs.lib;
rust =
(pkgs.rustChannelOf {
- date = "2023-10-28";
+ date = "2024-01-10";
channel = "nightly";
})
.default
.override {
extensions = ["rust-src" "miri" "rust-analyzer" "clippy"];
};
- in rec {
+ in {
packages = rec {
go-jsonnet = pkgs.callPackage ./nix/go-jsonnet.nix {};
sjsonnet = pkgs.callPackage ./nix/sjsonnet.nix {};
tests/suite/std_param_names.jsonnetdiffbeforeafterboth--- a/tests/suite/std_param_names.jsonnet
+++ b/tests/suite/std_param_names.jsonnet
@@ -103,6 +103,7 @@
asin: ['x'],
acos: ['x'],
atan: ['x'],
+ atan2: ['y', 'x'],
type: ['x'],
filter: ['func', 'arr'],
objectHasEx: ['obj', 'fname', 'hidden'],