difftreelog
feat(fmt) single-line objects
in: master
6 files changed
crates/jrsonnet-formatter/src/children.rsdiffbeforeafterboth1// TODO: Return errors as trivia23use std::{fmt::Debug, mem};45use jrsonnet_rowan_parser::{6 nodes::{CustomError, Trivia, TriviaKind},7 AstNode, AstToken, SyntaxElement, SyntaxNode, TS,8};910pub type ChildTrivia = Vec<Result<Trivia, String>>;1112/// Node should have no non-trivia tokens before element13pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {14 let mut out = Vec::new();15 for item in node.children_with_tokens() {16 if Some(&item) == end {17 break;18 }1920 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {21 out.push(Ok(trivia));22 } else if CustomError::can_cast(item.kind()) {23 out.push(Err(item.to_string()));24 } else if end.is_none() {25 break;26 } else {27 assert!(28 TS![, ;].contains(item.kind()),29 "silently eaten token: {:?}",30 item.kind()31 );32 }33 }34 out35}36/// Node should have no non-trivia tokens after element37pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {38 if start.is_none() {39 return Vec::new();40 }41 let mut iter = node.children_with_tokens().peekable();42 while iter.peek() != start {43 iter.next();44 }45 iter.next();46 let mut out = Vec::new();47 for item in iter {48 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {49 out.push(Ok(trivia));50 } else if CustomError::can_cast(item.kind()) {51 out.push(Err(item.to_string()));52 } else {53 assert!(54 TS![, ;].contains(item.kind()),55 "silently eaten token: {:?}",56 item.kind()57 );58 }59 }60 out61}6263pub fn children_between<T: AstNode + Debug>(64 node: SyntaxNode,65 start: Option<&SyntaxElement>,66 end: Option<&SyntaxElement>,67 trailing: Option<ChildTrivia>,68) -> (Vec<Child<T>>, EndingComments) {69 let mut iter = node.children_with_tokens().peekable();70 if start.is_some() {71 while iter.peek() != start {72 iter.next();73 }74 iter.next();75 }76 children(77 iter.take_while(|i| Some(i) != end),78 start.is_none() && end.is_none(),79 trailing,80 )81}8283pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {84 count_newlines_before(tt)85 + prev_inline86 .map(count_newlines_after)87 .unwrap_or_default()8889 // First for previous item end, second for current item90 >= 291}9293fn count_newlines_before(tt: &ChildTrivia) -> usize {94 let mut nl_count = 0;95 for t in tt {96 match t {97 Ok(t) => match t.kind() {98 TriviaKind::Whitespace => {99 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();100 }101 _ => break,102 },103 Err(_) => {104 nl_count += 1;105 }106 }107 }108 nl_count109}110fn count_newlines_after(tt: &ChildTrivia) -> usize {111 let mut nl_count = 0;112 for t in tt.iter().rev() {113 match t {114 Ok(t) => match t.kind() {115 TriviaKind::Whitespace => {116 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();117 }118 TriviaKind::SingleLineHashComment | TriviaKind::SingleLineSlashComment => {119 nl_count += 1;120 break;121 }122 _ => {}123 },124 Err(_) => nl_count += 1,125 }126 }127 nl_count128}129130pub fn children<T: AstNode + Debug>(131 items: impl Iterator<Item = SyntaxElement>,132 loose: bool,133 mut trailing: Option<ChildTrivia>,134) -> (Vec<Child<T>>, EndingComments) {135 let mut out = Vec::new();136 let mut current_child = None::<Child<T>>;137 let mut next = ChildTrivia::new();138 // Previous element ended, do not add more inline comments139 let mut started_next = false;140 let mut had_some = false;141142 for item in items {143 if let Some(value) = item.as_node().cloned().and_then(T::cast) {144 let before_trivia = if let Some(trailing) = trailing.take() {145 assert!(next.is_empty());146 trailing147 } else {148 mem::take(&mut next)149 };150 let last_child = current_child.replace(Child {151 // First item should not start with newline152 should_start_with_newline: had_some153 && should_start_with_newline(154 current_child.as_ref().map(|c| &c.inline_trivia),155 &before_trivia,156 ),157 before_trivia,158 value,159 inline_trivia: Vec::new(),160 });161 if let Some(last_child) = last_child {162 out.push(last_child);163 }164 had_some = true;165 started_next = false;166 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {167 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment168 || trivia.kind() == TriviaKind::SingleLineSlashComment;169 if trailing.is_some() {170 // Someone have already parsed trivia for us171 continue;172 } else if started_next173 || current_child.is_none()174 || trivia.text().contains('\n') && !is_single_line_comment175 {176 next.push(Ok(trivia.clone()));177 started_next = true;178 } else {179 let cur = current_child.as_mut().expect("checked not none");180 cur.inline_trivia.push(Ok(trivia));181 if is_single_line_comment {182 started_next = true;183 }184 }185 had_some = true;186 } else if CustomError::can_cast(item.kind()) {187 next.push(Err(item.to_string()));188 } else if loose {189 if had_some {190 break;191 }192 started_next = true;193 } else {194 assert!(195 TS![, ;].contains(item.kind()),196 "silently eaten token: {:?}",197 item.kind()198 );199 }200 }201202 let ending_comments = EndingComments {203 should_start_with_newline: should_start_with_newline(204 current_child.as_ref().map(|c| &c.inline_trivia),205 &next,206 ),207 trivia: next,208 };209210 if let Some(current_child) = current_child {211 out.push(current_child);212 }213214 (out, ending_comments)215}216217#[derive(Debug)]218pub struct Child<T> {219 /// If this child has two newlines above in source code, so it needs to have it in the output220 pub should_start_with_newline: bool,221 /// Comment before item, i.e222 ///223 /// ```ignore224 /// // Comment225 /// item226 /// ```227 pub before_trivia: ChildTrivia,228 pub value: T,229 /// Comment after line, but located at same line230 ///231 /// ```ignore232 /// item1, // Inline comment233 /// // Not inline comment234 /// item2,235 /// ```236 pub inline_trivia: ChildTrivia,237}238239pub struct EndingComments {240 /// If this child has two newlines above in source code, so it needs to have it in the output241 pub should_start_with_newline: bool,242 pub trivia: ChildTrivia,243}244impl EndingComments {245 pub fn is_empty(&self) -> bool {246 !self.should_start_with_newline && self.trivia.is_empty()247 }248 pub fn extract_trailing(&mut self) -> ChildTrivia {249 mem::take(&mut self.trivia)250 }251}1// TODO: Return errors as trivia23use std::{fmt::Debug, mem};45use jrsonnet_rowan_parser::{6 nodes::{CustomError, Trivia, TriviaKind},7 AstNode, AstToken, SyntaxElement, SyntaxNode, TS,8};910pub type ChildTrivia = Vec<Result<Trivia, String>>;1112/// Node should have no non-trivia tokens before element13pub fn trivia_before(node: SyntaxNode, end: Option<&SyntaxElement>) -> ChildTrivia {14 let mut out = Vec::new();15 for item in node.children_with_tokens() {16 if Some(&item) == end {17 break;18 }1920 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {21 out.push(Ok(trivia));22 } else if CustomError::can_cast(item.kind()) {23 out.push(Err(item.to_string()));24 } else if end.is_none() {25 break;26 } else {27 assert!(28 TS![, ;].contains(item.kind()),29 "silently eaten token: {:?}",30 item.kind()31 );32 }33 }34 out35}36/// Node should have no non-trivia tokens after element37pub fn trivia_after(node: SyntaxNode, start: Option<&SyntaxElement>) -> ChildTrivia {38 if start.is_none() {39 return Vec::new();40 }41 let mut iter = node.children_with_tokens().peekable();42 while iter.peek() != start {43 iter.next();44 }45 iter.next();46 let mut out = Vec::new();47 for item in iter {48 if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {49 out.push(Ok(trivia));50 } else if CustomError::can_cast(item.kind()) {51 out.push(Err(item.to_string()));52 } else {53 assert!(54 TS![, ;].contains(item.kind()),55 "silently eaten token: {:?}",56 item.kind()57 );58 }59 }60 out61}6263pub fn children_between<T: AstNode + Debug>(64 node: SyntaxNode,65 start: Option<&SyntaxElement>,66 end: Option<&SyntaxElement>,67 trailing: Option<ChildTrivia>,68) -> (Vec<Child<T>>, EndingComments) {69 let mut iter = node.children_with_tokens().peekable();70 if start.is_some() {71 while iter.peek() != start {72 iter.next();73 }74 iter.next();75 }76 children(77 iter.take_while(|i| Some(i) != end),78 start.is_none() && end.is_none(),79 trailing,80 )81}8283// Should start, triggers multi-line render84pub fn should_start_with_newline(85 prev_inline: Option<&ChildTrivia>,86 tt: &ChildTrivia,87) -> (bool, bool) {88 let count =89 count_newlines_before(tt) + prev_inline.map(count_newlines_after).unwrap_or_default();9091 (92 // First for previous item end, second for current item93 count >= 2,94 count >= 1,95 )96}9798fn count_newlines_before(tt: &ChildTrivia) -> usize {99 let mut nl_count = 0;100 for t in tt {101 match t {102 Ok(t) => match t.kind() {103 TriviaKind::Whitespace => {104 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();105 }106 _ => break,107 },108 Err(_) => {109 nl_count += 1;110 }111 }112 }113 nl_count114}115fn count_newlines_after(tt: &ChildTrivia) -> usize {116 let mut nl_count = 0;117 for t in tt.iter().rev() {118 match t {119 Ok(t) => match t.kind() {120 TriviaKind::Whitespace => {121 nl_count += t.text().bytes().filter(|b| *b == b'\n').count();122 }123 TriviaKind::SingleLineHashComment | TriviaKind::SingleLineSlashComment => {124 nl_count += 1;125 break;126 }127 _ => {}128 },129 Err(_) => nl_count += 1,130 }131 }132 nl_count133}134135pub fn children<T: AstNode + Debug>(136 items: impl Iterator<Item = SyntaxElement>,137 loose: bool,138 mut trailing: Option<ChildTrivia>,139) -> (Vec<Child<T>>, EndingComments) {140 let mut out = Vec::new();141 let mut current_child = None::<Child<T>>;142 let mut next = ChildTrivia::new();143 // Previous element ended, do not add more inline comments144 let mut started_next = false;145 let mut had_some = false;146147 for item in items {148 if let Some(value) = item.as_node().cloned().and_then(T::cast) {149 let before_trivia = if let Some(trailing) = trailing.take() {150 assert!(next.is_empty());151 trailing152 } else {153 mem::take(&mut next)154 };155 let (should_start_with_newline, triggers_multiline) = should_start_with_newline(156 current_child.as_ref().map(|c| &c.inline_trivia),157 &before_trivia,158 );159 let last_child = current_child.replace(Child {160 // First item should not start with newline161 should_start_with_newline: had_some && should_start_with_newline,162 triggers_multiline,163 before_trivia,164 value,165 inline_trivia: Vec::new(),166 });167 if let Some(last_child) = last_child {168 out.push(last_child);169 }170 had_some = true;171 started_next = false;172 } else if let Some(trivia) = item.as_token().cloned().and_then(Trivia::cast) {173 let is_single_line_comment = trivia.kind() == TriviaKind::SingleLineHashComment174 || trivia.kind() == TriviaKind::SingleLineSlashComment;175 if trailing.is_some() {176 // Someone have already parsed trivia for us177 continue;178 } else if started_next179 || current_child.is_none()180 || trivia.text().contains('\n') && !is_single_line_comment181 {182 next.push(Ok(trivia.clone()));183 started_next = true;184 } else {185 let cur = current_child.as_mut().expect("checked not none");186 cur.inline_trivia.push(Ok(trivia));187 if is_single_line_comment {188 started_next = true;189 }190 }191 had_some = true;192 } else if CustomError::can_cast(item.kind()) {193 next.push(Err(item.to_string()));194 } else if loose {195 if had_some {196 break;197 }198 started_next = true;199 } else {200 assert!(201 TS![, ;].contains(item.kind()),202 "silently eaten token: {:?}",203 item.kind()204 );205 }206 }207208 let ending_comments = EndingComments {209 should_start_with_newline: should_start_with_newline(210 current_child.as_ref().map(|c| &c.inline_trivia),211 &next,212 )213 .0,214 trivia: next,215 };216217 if let Some(current_child) = current_child {218 out.push(current_child);219 }220221 (out, ending_comments)222}223224#[derive(Debug)]225pub struct Child<T> {226 /// If this child has two newlines above in source code, so it needs to have it in the output227 pub should_start_with_newline: bool,228 /// Comment before item, i.e229 ///230 /// ```ignore231 /// // Comment232 /// item233 /// ```234 pub before_trivia: ChildTrivia,235 pub value: T,236 /// Comment after line, but located at same line237 ///238 /// ```ignore239 /// item1, // Inline comment240 /// // Not inline comment241 /// item2,242 /// ```243 pub inline_trivia: ChildTrivia,244 /// Is this child has whitespace that is considered significant, meaning245 /// user has inserted it to split the value into multiple lines.246 pub triggers_multiline: bool,247}248249pub struct EndingComments {250 /// If this child has two newlines above in source code, so it needs to have it in the output251 pub should_start_with_newline: bool,252 pub trivia: ChildTrivia,253}254impl EndingComments {255 pub fn is_empty(&self) -> bool {256 !self.should_start_with_newline && self.trivia.is_empty()257 }258 pub fn extract_trailing(&mut self) -> ChildTrivia {259 mem::take(&mut self.trivia)260 }261}crates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/comments.rs
+++ b/crates/jrsonnet-formatter/src/comments.rs
@@ -72,7 +72,10 @@
if matches!(loc, CommentLocation::ItemInline) {
p!(out, str(" "));
}
- p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl);
+ p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */"));
+ if matches!(loc, CommentLocation::AboveItem | CommentLocation::EndOfItems) {
+ p!(out, nl);
+ }
} else if !lines.is_empty() {
fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
let offset = a
crates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -3,8 +3,10 @@
use children::{children_between, trivia_before};
use dprint_core::formatting::{
condition_helpers::is_multiple_lines,
+ condition_resolvers::true_resolver,
ir_helpers::{new_line_group, with_indent},
- ConditionResolver, ConditionResolverContext, LineNumber, PrintItems, PrintOptions,
+ ConditionResolver, ConditionResolverContext, LineNumber, PrintItemPath, PrintItems,
+ PrintOptions, Signal,
};
use hi_doc::{Formatting, SnippetBuilder};
use jrsonnet_rowan_parser::{
@@ -12,13 +14,13 @@
Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,
- UnaryOperator, Visibility,
+ Trivia, TriviaKind, UnaryOperator, Visibility,
},
AstNode, AstToken as _, SyntaxToken,
};
use crate::{
- children::{trivia_after, Child},
+ children::{trivia_after, Child, EndingComments},
comments::{format_comments, CommentLocation},
};
@@ -27,6 +29,23 @@
#[cfg(test)]
mod tests;
+fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {
+ let end_comments_items = {
+ let mut items = PrintItems::new();
+ if e.should_start_with_newline {
+ p!(&mut items, nl);
+ }
+ format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);
+ items.into_rc_path()
+ };
+ let items =
+ new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();
+
+ let indented = with_indent(pi!(@i; nl items(items.into())));
+
+ pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))
+}
+
pub trait Printable {
fn print(&self, out: &mut PrintItems);
}
@@ -147,6 +166,10 @@
($o:ident, $($t:tt)*) => {
pi!(@s; $o: $($t)*)
};
+ (&mut $o:ident, $($t:tt)*) => {
+ let om = &mut $o;
+ pi!(@s; om: $($t)*)
+ };
}
pub(crate) use p;
pub(crate) use pi;
@@ -461,22 +484,53 @@
p!(out, str("{ }"));
return;
}
- p!(out, str("{") >i nl);
- for (i, mem) in children.into_iter().enumerate() {
- if mem.should_start_with_newline && i != 0 {
- p!(out, nl);
+
+ let source_is_multiline = children.iter().any(|c| c.triggers_multiline)
+ || end_comments.should_start_with_newline;
+
+ let start = LineNumber::new("obj start line");
+ let end = LineNumber::new("obj end line");
+ let multi_line: ConditionResolver = if source_is_multiline {
+ true_resolver()
+ } else {
+ Rc::new(move |ctx: &mut ConditionResolverContext| {
+ is_multiple_lines(ctx, start, end)
+ })
+ };
+
+ fn gen_members(
+ children: Vec<Child<Member>>,
+ multi_line: ConditionResolver,
+ ) -> PrintItems {
+ let mut _out = PrintItems::new();
+ let out = &mut _out;
+ let mut members = children.into_iter().peekable();
+ while let Some(mem) = members.next() {
+ if mem.should_start_with_newline {
+ p!(out, nl);
+ }
+ format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
+ p!(out, { mem.value });
+ let has_more = members.peek().is_some();
+ if has_more {
+ p!(out, str(","));
+ } else {
+ p!(out, if("trailing comma", multi_line, str(",")));
+ }
+ format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
+ p!(out, if_else("member separator", multi_line, nl)(sonl));
}
- format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
- p!(out, {mem.value} str(","));
- format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
- p!(out, nl);
+ _out
}
- if end_comments.should_start_with_newline {
- p!(out, nl);
- }
- format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);
- p!(out, <i str("}"));
+ let members_items =
+ new_line_group(gen_members(children, multi_line.clone())).into_rc_path();
+
+ let members = with_indent_eoi(multi_line, members_items.into(), end_comments);
+
+ p!(out, str("{") info(start));
+ p!(out, items(members));
+ p!(out, str("}") info(end));
}
}
}
crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snapdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snap
@@ -1,6 +1,6 @@
---
source: crates/jrsonnet-formatter/src/tests.rs
-expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n }\"))"
+expression: "reformat(indoc!(\"{\n\t\t comments: {\n\t\t\t_: '',\n\t\t\t// Plain comment\n\t\t\ta: '',\n\n\t\t\t# Plain comment with empty line before\n\t\t\tb: '',\n\t\t\t/*Single-line multiline comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Single-line multiline doc comment\n\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/**Multiline doc\n\t\t\tComment\n\t\t\t*/\n\t\t\tc: '',\n\n\t\t\t/*\n\n\tMulti-line\n\n\tcomment\n\t\t\t*/\n\t\t\td: '',\n\n\t\t\te: '', // Inline comment\n\n\t\t\tk: '',\n\n\t\t\t// Text after everything\n\t\t },\n\t\t comments2: {\n\t\t\tk: '',\n\t\t\t// Text after everything, but no newline above\n\t\t },\n spacing: {\n a: '',\n\n b: '',\n },\n noSpacing: {\n a: '',\n b: '',\n },\n\n\t\t\t smallObjectWithEnding: {/*Ending comment*/},\n\t\t\t smallObjectWithFieldAndEnding: {a: 11/*Ending comment*/},\n\t\t\t smallObjectWithFieldAndEnding2: {/*Start*/a: 11/*Ending comment*/},\n }\"))"
---
{
comments: {
@@ -50,4 +50,13 @@
a: '',
b: '',
},
+
+ smallObjectWithEnding: {
+ /* Ending comment */
+ },
+ smallObjectWithFieldAndEnding: { a: 11 /* Ending comment */ },
+ smallObjectWithFieldAndEnding2: {
+ /* Start */
+ a: 11, /* Ending comment */
+ },
}
crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_nested.snapdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_nested.snap
@@ -0,0 +1,41 @@
+---
+source: crates/jrsonnet-formatter/src/tests.rs
+expression: "reformat(indoc!(\"\n\t\t\t{\n\t\t\t\tkubernetes: {\n\t\t\t\t deployment: {\n\t\t\t\t\tapiVersion: 'apps/v1',\n\t\t\t\t\tkind: 'Deployment',\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t name: 'myapp',\n\t\t\t\t\t labels: { app: 'myapp', version: 'v1' },\n\t\t\t\t\t},\n\t\t\t\t\tspec: {\n\t\t\t\t\t replicas: 3,\n\t\t\t\t\t selector: { matchLabels: { app: 'myapp' } },\n\t\t\t\t\t template: {\n\t\t\t\t\t\t metadata: { labels: { app: 'myapp' } },\n\t\t\t\t\t\t spec: {\n\t\t\t\t\t\t\tcontainers: [\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t name: 'myapp',\n\t\t\t\t\t\t\t\t image: 'myapp:latest',\n\t\t\t\t\t\t\t\t ports: [{ containerPort: 8080 }],\n\t\t\t\t\t\t\t\t env: [\n\t\t\t\t\t\t\t\t\t{ name: 'FOO', value: 'bar' },\n\t\t\t\t\t\t\t\t\t{ name: 'BAZ', valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } } },\n\t\t\t\t\t\t\t\t ],\n\t\t\t\t\t\t\t },\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t },\n\t\t\t\t\t },\n\t\t\t\t\t},\n\t\t\t\t },\n\t\t\t },\n\t\t\t}\n\t\t\"))"
+---
+{
+ kubernetes: {
+ deployment: {
+ apiVersion: 'apps/v1',
+ kind: 'Deployment',
+ metadata: {
+ name: 'myapp',
+ labels: { app: 'myapp', version: 'v1' },
+ },
+ spec: {
+ replicas: 3,
+ selector: { matchLabels: { app: 'myapp' } },
+ template: {
+ metadata: { labels: { app: 'myapp' } },
+ spec: {
+ containers: [
+ {
+ name: 'myapp',
+ image: 'myapp:latest',
+ ports: [
+ { containerPort: 8080 },
+ ],
+ env: [
+ { name: 'FOO', value: 'bar' },
+ {
+ name: 'BAZ',
+ valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } },
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+}
crates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/tests.rs
+++ b/crates/jrsonnet-formatter/src/tests.rs
@@ -74,6 +74,10 @@
a: '',
b: '',
},
+
+ smallObjectWithEnding: {/*Ending comment*/},
+ smallObjectWithFieldAndEnding: {a: 11/*Ending comment*/},
+ smallObjectWithFieldAndEnding2: {/*Start*/a: 11/*Ending comment*/},
}"
)));
}
@@ -104,3 +108,43 @@
"
)));
}
+
+#[test]
+fn complex_nested() {
+ insta::assert_snapshot!(reformat(indoc!(
+ "
+ {
+ kubernetes: {
+ deployment: {
+ apiVersion: 'apps/v1',
+ kind: 'Deployment',
+ metadata: {
+ name: 'myapp',
+ labels: { app: 'myapp', version: 'v1' },
+ },
+ spec: {
+ replicas: 3,
+ selector: { matchLabels: { app: 'myapp' } },
+ template: {
+ metadata: { labels: { app: 'myapp' } },
+ spec: {
+ containers: [
+ {
+ name: 'myapp',
+ image: 'myapp:latest',
+ ports: [{ containerPort: 8080 }],
+ env: [
+ { name: 'FOO', value: 'bar' },
+ { name: 'BAZ', valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } } },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ }
+ "
+ )));
+}