Commit a130eac785

tjohnes <tom@johnes.me>
2021-10-25 10:59:45
zig fmt: fix formatting for single-line containers with comments
* Fixes #8810. * Prevent a single-line container declaration if it contains a comment or multiline string. * If a container declaration cannot be single-line, ensure container fields are rendered with a trailing comma. * If `Space.comma` is passed to `renderExpressionComma` or `renderTokenComma`, and there already exists a comma in the source, then render one comma instead of two.
1 parent 36c8adf
Changed files (2)
lib/std/zig/parser_test.zig
@@ -329,6 +329,50 @@ test "zig fmt: container declaration, transform trailing comma" {
     );
 }
 
+test "zig fmt: container declaration, comment, add trailing comma" {
+    try testTransform(
+        \\const X = struct {
+        \\    foo: i32, // foo
+        \\    bar: i8
+        \\};
+    ,
+        \\const X = struct {
+        \\    foo: i32, // foo
+        \\    bar: i8,
+        \\};
+        \\
+    );
+    try testTransform(
+        \\const X = struct {
+        \\    foo: i32 // foo
+        \\};
+    ,
+        \\const X = struct {
+        \\    foo: i32, // foo
+        \\};
+        \\
+    );
+}
+
+test "zig fmt: container declaration, multiline string, add trailing comma" {
+    try testTransform(
+        \\const X = struct {
+        \\    foo: []const u8 =
+        \\        \\ foo
+        \\    ,
+        \\    bar: i8
+        \\};
+    ,
+        \\const X = struct {
+        \\    foo: []const u8 =
+        \\        \\ foo
+        \\    ,
+        \\    bar: i8,
+        \\};
+        \\
+    );
+}
+
 test "zig fmt: remove empty lines at start/end of container decl" {
     try testTransform(
         \\const X = struct {
lib/std/zig/render.zig
@@ -1206,7 +1206,7 @@ fn renderContainerField(
         ais.pushIndent();
         try renderExpression(gpa, ais, tree, field.ast.value_expr, .none); // value
         ais.popIndent();
-        try renderToken(ais, tree, maybe_comma, space);
+        try renderToken(ais, tree, maybe_comma, .newline);
     } else {
         ais.pushIndent();
         try renderExpression(gpa, ais, tree, field.ast.value_expr, space); // value
@@ -1894,7 +1894,11 @@ fn renderContainerDecl(
 
     const src_has_trailing_comma = token_tags[rbrace - 1] == .comma;
     if (!src_has_trailing_comma) one_line: {
-        // We can only print all the members in-line if all the members are fields.
+        // We can only print all the members in-line if there are no comments or multiline strings,
+        // and all the members are fields.
+        if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) {
+            break :one_line;
+        }
         for (container_decl.ast.members) |member| {
             if (!node_tags[member].isContainerField()) break :one_line;
         }
@@ -1912,7 +1916,18 @@ fn renderContainerDecl(
     if (token_tags[lbrace + 1] == .container_doc_comment) {
         try renderContainerDocComments(ais, tree, lbrace + 1);
     }
-    try renderMembers(gpa, ais, tree, container_decl.ast.members);
+    for (container_decl.ast.members) |member, i| {
+        if (i != 0) try renderExtraNewline(ais, tree, member);
+        switch (tree.nodes.items(.tag)[member]) {
+            // For container fields, ensure a trailing comma is added if necessary.
+            .container_field_init,
+            .container_field_align,
+            .container_field,
+            => try renderMember(gpa, ais, tree, member, .comma),
+
+            else => try renderMember(gpa, ais, tree, member, .newline),
+        }
+    }
     ais.popIndent();
 
     return renderToken(ais, tree, rbrace, space); // rbrace
@@ -2200,10 +2215,11 @@ fn renderExpressionIndented(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Nod
 }
 
 /// Render an expression, and the comma that follows it, if it is present in the source.
+/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
 fn renderExpressionComma(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.Index, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);
     const maybe_comma = tree.lastToken(node) + 1;
-    if (token_tags[maybe_comma] == .comma) {
+    if (token_tags[maybe_comma] == .comma and space != .comma) {
         try renderExpression(gpa, ais, tree, node, .none);
         return renderToken(ais, tree, maybe_comma, space);
     } else {
@@ -2211,10 +2227,12 @@ fn renderExpressionComma(gpa: *Allocator, ais: *Ais, tree: Ast, node: Ast.Node.I
     }
 }
 
+/// Render a token, and the comma that follows it, if it is present in the source.
+/// If a comma is present, and `space` is `Space.comma`, render only a single comma.
 fn renderTokenComma(ais: *Ais, tree: Ast, token: Ast.TokenIndex, space: Space) Error!void {
     const token_tags = tree.tokens.items(.tag);
     const maybe_comma = token + 1;
-    if (token_tags[maybe_comma] == .comma) {
+    if (token_tags[maybe_comma] == .comma and space != .comma) {
         try renderToken(ais, tree, token, .none);
         return renderToken(ais, tree, maybe_comma, space);
     } else {