git.delta.rocks / jrsonnet / refs/commits / 46ef62a1f872

difftreelog

feat(fmt) single-line objects

unzqvprpYaroslav Bolyukin2026-02-08parent: #9b1cb43.patch.diff
in: master

6 files changed

modifiedcrates/jrsonnet-formatter/src/children.rsdiffbeforeafterboth
80 )80 )
81}81}
8282
83// Should start, triggers multi-line render
83pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {84pub fn should_start_with_newline(
85 prev_inline: Option<&ChildTrivia>,
86 tt: &ChildTrivia,
87) -> (bool, bool) {
88 let count =
84 count_newlines_before(tt)89 count_newlines_before(tt) + prev_inline.map(count_newlines_after).unwrap_or_default();
85 + prev_inline90
86 .map(count_newlines_after)91 (
87 .unwrap_or_default()
88
89 // First for previous item end, second for current item92 // First for previous item end, second for current item
90 >= 293 count >= 2,
94 count >= 1,
95 )
91}96}
9297
93fn count_newlines_before(tt: &ChildTrivia) -> usize {98fn count_newlines_before(tt: &ChildTrivia) -> usize {
147 } else {152 } else {
148 mem::take(&mut next)153 mem::take(&mut next)
149 };154 };
150 let last_child = current_child.replace(Child {155 let (should_start_with_newline, triggers_multiline) = should_start_with_newline(
151 // First item should not start with newline
152 should_start_with_newline: had_some
153 && should_start_with_newline(
154 current_child.as_ref().map(|c| &c.inline_trivia),156 current_child.as_ref().map(|c| &c.inline_trivia),
155 &before_trivia,157 &before_trivia,
156 ),158 );
159 let last_child = current_child.replace(Child {
160 // First item should not start with newline
161 should_start_with_newline: had_some && should_start_with_newline,
162 triggers_multiline,
157 before_trivia,163 before_trivia,
158 value,164 value,
159 inline_trivia: Vec::new(),165 inline_trivia: Vec::new(),
160 });166 });
161 if let Some(last_child) = last_child {167 if let Some(last_child) = last_child {
162 out.push(last_child);168 out.push(last_child);
163 }169 }
204 current_child.as_ref().map(|c| &c.inline_trivia),210 current_child.as_ref().map(|c| &c.inline_trivia),
205 &next,211 &next,
206 ),212 )
213 .0,
207 trivia: next,214 trivia: next,
208 };215 };
209216
234 /// item2,241 /// item2,
235 /// ```242 /// ```
236 pub inline_trivia: ChildTrivia,243 pub inline_trivia: ChildTrivia,
244 /// Is this child has whitespace that is considered significant, meaning
245 /// user has inserted it to split the value into multiple lines.
246 pub triggers_multiline: bool,
237}247}
238248
239pub struct EndingComments {249pub struct EndingComments {
modifiedcrates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth
72 if matches!(loc, CommentLocation::ItemInline) {72 if matches!(loc, CommentLocation::ItemInline) {
73 p!(out, str(" "));73 p!(out, str(" "));
74 }74 }
75 p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */") nl);75 p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */"));
76 if matches!(loc, CommentLocation::AboveItem | CommentLocation::EndOfItems) {
77 p!(out, nl);
78 }
76 } else if !lines.is_empty() {79 } else if !lines.is_empty() {
77 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {80 fn common_ws_prefix<'a>(a: &'a str, b: &str) -> &'a str {
78 let offset = a81 let offset = a
modifiedcrates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth
3use children::{children_between, trivia_before};3use children::{children_between, trivia_before};
4use dprint_core::formatting::{4use dprint_core::formatting::{
5 condition_helpers::is_multiple_lines,5 condition_helpers::is_multiple_lines,
6 condition_resolvers::true_resolver,
6 ir_helpers::{new_line_group, with_indent},7 ir_helpers::{new_line_group, with_indent},
7 ConditionResolver, ConditionResolverContext, LineNumber, PrintItems, PrintOptions,8 ConditionResolver, ConditionResolverContext, LineNumber, PrintItemPath, PrintItems,
9 PrintOptions, Signal,
8};10};
9use hi_doc::{Formatting, SnippetBuilder};11use hi_doc::{Formatting, SnippetBuilder};
10use jrsonnet_rowan_parser::{12use jrsonnet_rowan_parser::{
11 nodes::{13 nodes::{
12 Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,14 Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
13 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,15 DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
14 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,16 Name, Number, ObjBody, ObjLocal, ParamsDesc, SliceDesc, SourceFile, Stmt, Suffix, Text,
15 UnaryOperator, Visibility,17 Trivia, TriviaKind, UnaryOperator, Visibility,
16 },18 },
17 AstNode, AstToken as _, SyntaxToken,19 AstNode, AstToken as _, SyntaxToken,
18};20};
1921
20use crate::{22use crate::{
21 children::{trivia_after, Child},23 children::{trivia_after, Child, EndingComments},
22 comments::{format_comments, CommentLocation},24 comments::{format_comments, CommentLocation},
23};25};
2426
27#[cfg(test)]29#[cfg(test)]
28mod tests;30mod tests;
31
32fn with_indent_eoi(cond: ConditionResolver, o: PrintItems, e: EndingComments) -> PrintItems {
33 let end_comments_items = {
34 let mut items = PrintItems::new();
35 if e.should_start_with_newline {
36 p!(&mut items, nl);
37 }
38 format_comments(&e.trivia, CommentLocation::EndOfItems, &mut items);
39 items.into_rc_path()
40 };
41 let items =
42 new_line_group(pi!(@i; items(o.into()) items(end_comments_items.into()))).into_rc_path();
43
44 let indented = with_indent(pi!(@i; nl items(items.into())));
45
46 pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))
47}
2948
30pub trait Printable {49pub trait Printable {
31 fn print(&self, out: &mut PrintItems);50 fn print(&self, out: &mut PrintItems);
147 ($o:ident, $($t:tt)*) => {166 ($o:ident, $($t:tt)*) => {
148 pi!(@s; $o: $($t)*)167 pi!(@s; $o: $($t)*)
149 };168 };
169 (&mut $o:ident, $($t:tt)*) => {
170 let om = &mut $o;
171 pi!(@s; om: $($t)*)
172 };
150}173}
151pub(crate) use p;174pub(crate) use p;
152pub(crate) use pi;175pub(crate) use pi;
462 return;485 return;
463 }486 }
487
488 let source_is_multiline = children.iter().any(|c| c.triggers_multiline)
489 || end_comments.should_start_with_newline;
490
491 let start = LineNumber::new("obj start line");
492 let end = LineNumber::new("obj end line");
493 let multi_line: ConditionResolver = if source_is_multiline {
494 true_resolver()
495 } else {
464 p!(out, str("{") >i nl);496 Rc::new(move |ctx: &mut ConditionResolverContext| {
497 is_multiple_lines(ctx, start, end)
498 })
499 };
500
465 for (i, mem) in children.into_iter().enumerate() {501 fn gen_members(
502 children: Vec<Child<Member>>,
503 multi_line: ConditionResolver,
504 ) -> PrintItems {
505 let mut _out = PrintItems::new();
506 let out = &mut _out;
507 let mut members = children.into_iter().peekable();
508 while let Some(mem) = members.next() {
466 if mem.should_start_with_newline && i != 0 {509 if mem.should_start_with_newline {
467 p!(out, nl);510 p!(out, nl);
468 }511 }
469 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);512 format_comments(&mem.before_trivia, CommentLocation::AboveItem, out);
470 p!(out, {mem.value} str(","));513 p!(out, { mem.value });
514 let has_more = members.peek().is_some();
515 if has_more {
516 p!(out, str(","));
517 } else {
518 p!(out, if("trailing comma", multi_line, str(",")));
519 }
471 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);520 format_comments(&mem.inline_trivia, CommentLocation::ItemInline, out);
472 p!(out, nl);521 p!(out, if_else("member separator", multi_line, nl)(sonl));
473 }522 }
474523 _out
475 if end_comments.should_start_with_newline {524 }
525
526 let members_items =
527 new_line_group(gen_members(children, multi_line.clone())).into_rc_path();
528
529 let members = with_indent_eoi(multi_line, members_items.into(), end_comments);
530
476 p!(out, nl);531 p!(out, str("{") info(start));
477 }
478 format_comments(&end_comments.trivia, CommentLocation::EndOfItems, out);532 p!(out, items(members));
479 p!(out, <i str("}"));533 p!(out, str("}") info(end));
480 }534 }
481 }535 }
482 }536 }
modifiedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_comments.snapdiffbeforeafterboth
1---1---
2source: crates/jrsonnet-formatter/src/tests.rs2source: crates/jrsonnet-formatter/src/tests.rs
3expression: "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 }\"))"3expression: "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 }\"))"
4---4---
5{5{
6 comments: {6 comments: {
50 a: '',50 a: '',
51 b: '',51 b: '',
52 },52 },
53
54 smallObjectWithEnding: {
55 /* Ending comment */
56 },
57 smallObjectWithFieldAndEnding: { a: 11 /* Ending comment */ },
58 smallObjectWithFieldAndEnding2: {
59 /* Start */
60 a: 11, /* Ending comment */
61 },
53}62}
5463
addedcrates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__complex_nested.snapdiffbeforeafterboth

no changes

modifiedcrates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth
24#[test]24#[test]
25fn complex_comments() {25fn complex_comments() {
26 insta::assert_snapshot!(reformat(indoc!(26 insta::assert_snapshot!(reformat(indoc!(
27 "{27 "{
28 comments: {28 comments: {
29 _: '',29 _: '',
30 // Plain comment30 // Plain comment
31 a: '',31 a: '',
3232
33 # Plain comment with empty line before33 # Plain comment with empty line before
34 b: '',34 b: '',
35 /*Single-line multiline comment35 /*Single-line multiline comment
3636
37 */37 */
38 c: '',38 c: '',
3939
40 /**Single-line multiline doc comment40 /**Single-line multiline doc comment
4141
42 */42 */
43 c: '',43 c: '',
4444
45 /**Multiline doc45 /**Multiline doc
46 Comment46 Comment
47 */47 */
48 c: '',48 c: '',
4949
50 /*50 /*
5151
52 Multi-line52 Multi-line
5353
54 comment54 comment
55 */55 */
56 d: '',56 d: '',
5757
58 e: '', // Inline comment58 e: '', // Inline comment
5959
60 k: '',60 k: '',
6161
62 // Text after everything62 // Text after everything
63 },63 },
64 comments2: {64 comments2: {
65 k: '',65 k: '',
66 // Text after everything, but no newline above66 // Text after everything, but no newline above
67 },67 },
68 spacing: {68 spacing: {
69 a: '',69 a: '',
7070
71 b: '',71 b: '',
72 },72 },
73 noSpacing: {73 noSpacing: {
74 a: '',74 a: '',
75 b: '',75 b: '',
76 },76 },
77
78 smallObjectWithEnding: {/*Ending comment*/},
79 smallObjectWithFieldAndEnding: {a: 11/*Ending comment*/},
80 smallObjectWithFieldAndEnding2: {/*Start*/a: 11/*Ending comment*/},
77 }"81 }"
78 )));82 )));
79}83}
8084
105 )));109 )));
106}110}
111
112#[test]
113fn complex_nested() {
114 insta::assert_snapshot!(reformat(indoc!(
115 "
116 {
117 kubernetes: {
118 deployment: {
119 apiVersion: 'apps/v1',
120 kind: 'Deployment',
121 metadata: {
122 name: 'myapp',
123 labels: { app: 'myapp', version: 'v1' },
124 },
125 spec: {
126 replicas: 3,
127 selector: { matchLabels: { app: 'myapp' } },
128 template: {
129 metadata: { labels: { app: 'myapp' } },
130 spec: {
131 containers: [
132 {
133 name: 'myapp',
134 image: 'myapp:latest',
135 ports: [{ containerPort: 8080 }],
136 env: [
137 { name: 'FOO', value: 'bar' },
138 { name: 'BAZ', valueFrom: { secretKeyRef: { name: 'mysecret', key: 'password' } } },
139 ],
140 },
141 ],
142 },
143 },
144 },
145 },
146 },
147 }
148 "
149 )));
150}
107151