difftreelog
feat(fmt) single-line objects
in: master
6 files changed
crates/jrsonnet-formatter/src/children.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/children.rs
+++ b/crates/jrsonnet-formatter/src/children.rs
@@ -80,14 +80,19 @@
)
}
-pub fn should_start_with_newline(prev_inline: Option<&ChildTrivia>, tt: &ChildTrivia) -> bool {
- count_newlines_before(tt)
- + prev_inline
- .map(count_newlines_after)
- .unwrap_or_default()
+// Should start, triggers multi-line render
+pub fn should_start_with_newline(
+ prev_inline: Option<&ChildTrivia>,
+ tt: &ChildTrivia,
+) -> (bool, bool) {
+ let count =
+ count_newlines_before(tt) + prev_inline.map(count_newlines_after).unwrap_or_default();
+ (
// First for previous item end, second for current item
- >= 2
+ count >= 2,
+ count >= 1,
+ )
}
fn count_newlines_before(tt: &ChildTrivia) -> usize {
@@ -147,13 +152,14 @@
} else {
mem::take(&mut next)
};
+ let (should_start_with_newline, triggers_multiline) = should_start_with_newline(
+ current_child.as_ref().map(|c| &c.inline_trivia),
+ &before_trivia,
+ );
let last_child = current_child.replace(Child {
// First item should not start with newline
- should_start_with_newline: had_some
- && should_start_with_newline(
- current_child.as_ref().map(|c| &c.inline_trivia),
- &before_trivia,
- ),
+ should_start_with_newline: had_some && should_start_with_newline,
+ triggers_multiline,
before_trivia,
value,
inline_trivia: Vec::new(),
@@ -203,7 +209,8 @@
should_start_with_newline: should_start_with_newline(
current_child.as_ref().map(|c| &c.inline_trivia),
&next,
- ),
+ )
+ .0,
trivia: next,
};
@@ -234,6 +241,9 @@
/// item2,
/// ```
pub inline_trivia: ChildTrivia,
+ /// Is this child has whitespace that is considered significant, meaning
+ /// user has inserted it to split the value into multiple lines.
+ pub triggers_multiline: bool,
}
pub struct EndingComments {
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.rsdiffbeforeafterboth3use 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};192120use 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};242627#[cfg(test)]29#[cfg(test)]28mod tests;30mod tests;3132fn 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();4344 let indented = with_indent(pi!(@i; nl items(items.into())));4546 pi!(@i; if_else("indented body", cond, items(indented))(str(" ") items(items.into())))47}294830pub 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 }487488 let source_is_multiline = children.iter().any(|c| c.triggers_multiline)489 || end_comments.should_start_with_newline;490491 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 };500465 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 _out475 if end_comments.should_start_with_newline {524 }525526 let members_items =527 new_line_group(gen_members(children, multi_line.clone())).into_rc_path();528529 let members = with_indent_eoi(multi_line, members_items.into(), end_comments);530476 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 }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' } } },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ }
+ "
+ )));
+}