Commit 8309ee8752

LemonBoy <thatlemon@gmail.com>
2020-02-01 11:28:21
fmt: Respect trailing comma for field declarations
Closes #4349
1 parent e6a812c
Changed files (3)
lib/std/zig/ast.zig
@@ -10,6 +10,7 @@ pub const TokenIndex = usize;
 pub const Tree = struct {
     source: []const u8,
     tokens: TokenList,
+
     /// undefined on parse error (errors not empty)
     root_node: *Node.Root,
     arena_allocator: std.heap.ArenaAllocator,
@@ -780,6 +781,11 @@ pub const Node = struct {
                 i -= 1;
             }
 
+            if (self.align_expr) |align_expr| {
+                if (i < 1) return align_expr;
+                i -= 1;
+            }
+
             if (self.value_expr) |value_expr| {
                 if (i < 1) return value_expr;
                 i -= 1;
@@ -796,6 +802,11 @@ pub const Node = struct {
             if (self.value_expr) |value_expr| {
                 return value_expr.lastToken();
             }
+            if (self.align_expr) |align_expr| {
+                // The expression refers to what's inside the parenthesis, the
+                // last token is the closing one
+                return align_expr.lastToken() + 1;
+            }
             if (self.type_expr) |type_expr| {
                 return type_expr.lastToken();
             }
lib/std/zig/parser_test.zig
@@ -1,3 +1,14 @@
+test "zig fmt: trailing comma in container declaration" {
+    try testCanonical(
+        \\const X = struct { foo: i32 };
+        \\const X = struct { foo: i32, bar: i32 };
+        \\const X = struct { foo: i32 = 1, bar: i32 = 2 };
+        \\const X = struct { foo: i32 align(4), bar: i32 align(4) };
+        \\const X = struct { foo: i32 align(4) = 1, bar: i32 align(4) = 2 };
+        \\
+    );
+}
+
 test "zig fmt: trailing comma in fn parameter list" {
     try testCanonical(
         \\pub fn f(
@@ -727,10 +738,7 @@ test "zig fmt: enum decl with no trailing comma" {
     try testTransform(
         \\const StrLitKind = enum {Normal, C};
     ,
-        \\const StrLitKind = enum {
-        \\    Normal,
-        \\    C,
-        \\};
+        \\const StrLitKind = enum { Normal, C };
         \\
     );
 }
@@ -989,11 +997,7 @@ test "zig fmt: no trailing comma on struct decl" {
         \\    k: usize, s: u32, t: u32
         \\};
     ,
-        \\const RoundParam = struct {
-        \\    k: usize,
-        \\    s: u32,
-        \\    t: u32,
-        \\};
+        \\const RoundParam = struct { k: usize, s: u32, t: u32 };
         \\
     );
 }
@@ -2560,10 +2564,8 @@ test "zig fmt: if type expr" {
     );
 }
 test "zig fmt: file ends with struct field" {
-    try testTransform(
+    try testCanonical(
         \\a: bool
-    ,
-        \\a: bool,
         \\
     );
 }
lib/std/zig/render.zig
@@ -206,6 +206,10 @@ fn renderExtraNewline(tree: *ast.Tree, stream: var, start_col: *usize, node: *as
 }
 
 fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Child.Error || Error)!void {
+    try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline);
+}
+
+fn renderContainerDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node, space: Space) (@TypeOf(stream).Child.Error || Error)!void {
     switch (decl.id) {
         .FnProto => {
             const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
@@ -213,11 +217,11 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
             try renderDocComments(tree, stream, fn_proto, indent, start_col);
 
             if (fn_proto.body_node) |body_node| {
-                try renderExpression(allocator, stream, tree, indent, start_col, decl, Space.Space);
-                try renderExpression(allocator, stream, tree, indent, start_col, body_node, Space.Newline);
+                try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space);
+                try renderExpression(allocator, stream, tree, indent, start_col, body_node, space);
             } else {
-                try renderExpression(allocator, stream, tree, indent, start_col, decl, Space.None);
-                try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, Space.Newline);
+                try renderExpression(allocator, stream, tree, indent, start_col, decl, .None);
+                try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, space);
             }
         },
 
@@ -225,11 +229,11 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
             const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl);
 
             if (use_decl.visib_token) |visib_token| {
-                try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub
+                try renderToken(tree, stream, visib_token, indent, start_col, .Space); // pub
             }
-            try renderToken(tree, stream, use_decl.use_token, indent, start_col, Space.Space); // usingnamespace
-            try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, Space.None);
-            try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, Space.Newline); // ;
+            try renderToken(tree, stream, use_decl.use_token, indent, start_col, .Space); // usingnamespace
+            try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, .None);
+            try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, space); // ;
         },
 
         .VarDecl => {
@@ -243,9 +247,9 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
             const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);
 
             try renderDocComments(tree, stream, test_decl, indent, start_col);
-            try renderToken(tree, stream, test_decl.test_token, indent, start_col, Space.Space);
-            try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, Space.Space);
-            try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, Space.Newline);
+            try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space);
+            try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space);
+            try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space);
         },
 
         .ContainerField => {
@@ -253,62 +257,76 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
 
             try renderDocComments(tree, stream, field, indent, start_col);
             if (field.comptime_token) |t| {
-                try renderToken(tree, stream, t, indent, start_col, Space.Space); // comptime
+                try renderToken(tree, stream, t, indent, start_col, .Space); // comptime
             }
 
+            const src_has_trailing_comma = blk: {
+                const maybe_comma = tree.nextToken(field.lastToken());
+                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+            };
+
+            // The trailing comma is emitted at the end, but if it's not present
+            // we still have to respect the specified `space` parameter
+            const last_token_space: Space = if (src_has_trailing_comma) .None else space;
+
             if (field.type_expr == null and field.value_expr == null) {
-                return renderToken(tree, stream, field.name_token, indent, start_col, Space.Comma); // name,
+                try renderToken(tree, stream, field.name_token, indent, start_col, last_token_space); // name
             } else if (field.type_expr != null and field.value_expr == null) {
-                try renderToken(tree, stream, field.name_token, indent, start_col, Space.None); // name
-                try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, Space.Space); // :
+                try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name
+                try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :
 
                 if (field.align_expr) |align_value_expr| {
-                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Space); // type
+                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
                     const lparen_token = tree.prevToken(align_value_expr.firstToken());
                     const align_kw = tree.prevToken(lparen_token);
                     const rparen_token = tree.nextToken(align_value_expr.lastToken());
-                    try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
-                    try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
-                    try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, Space.None); // alignment
-                    try renderToken(tree, stream, rparen_token, indent, start_col, Space.Comma); // ),
+                    try renderToken(tree, stream, align_kw, indent, start_col, .None); // align
+                    try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (
+                    try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment
+                    try renderToken(tree, stream, rparen_token, indent, start_col, last_token_space); // )
                 } else {
-                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Comma); // type,
+                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, last_token_space); // type
                 }
             } else if (field.type_expr == null and field.value_expr != null) {
-                try renderToken(tree, stream, field.name_token, indent, start_col, Space.Space); // name
-                try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, Space.Space); // =
-                return renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, Space.Comma); // value
+                try renderToken(tree, stream, field.name_token, indent, start_col, .Space); // name
+                try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // =
+                try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value
             } else {
-                try renderToken(tree, stream, field.name_token, indent, start_col, Space.None); // name
-                try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, Space.Space); // :
+                try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name
+                try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :
 
                 if (field.align_expr) |align_value_expr| {
-                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Space); // type
+                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
                     const lparen_token = tree.prevToken(align_value_expr.firstToken());
                     const align_kw = tree.prevToken(lparen_token);
                     const rparen_token = tree.nextToken(align_value_expr.lastToken());
-                    try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
-                    try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
-                    try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, Space.None); // alignment
-                    try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
+                    try renderToken(tree, stream, align_kw, indent, start_col, .None); // align
+                    try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (
+                    try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment
+                    try renderToken(tree, stream, rparen_token, indent, start_col, .Space); // )
                 } else {
-                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Space); // type
+                    try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
                 }
-                try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, Space.Space); // =
-                return renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, Space.Comma); // value,
+                try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, .Space); // =
+                try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value
+            }
+
+            if (src_has_trailing_comma) {
+                const comma = tree.nextToken(field.lastToken());
+                try renderToken(tree, stream, comma, indent, start_col, space);
             }
         },
 
         .Comptime => {
             assert(!decl.requireSemiColon());
-            try renderExpression(allocator, stream, tree, indent, start_col, decl, Space.Newline);
+            try renderExpression(allocator, stream, tree, indent, start_col, decl, space);
         },
 
         .DocComment => {
             const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl);
             var it = comment.lines.iterator(0);
             while (it.next()) |line_token_index| {
-                try renderToken(tree, stream, line_token_index.*, indent, start_col, Space.Newline);
+                try renderToken(tree, stream, line_token_index.*, indent, start_col, .Newline);
                 if (it.peek()) |_| {
                     try stream.writeByteNTimes(' ', indent);
                 }
@@ -1150,14 +1168,36 @@ fn renderExpression(
             if (container_decl.fields_and_decls.len == 0) {
                 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // {
                 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
-            } else {
+            }
+
+            const src_has_trailing_comma = blk: {
+                var maybe_comma = tree.prevToken(container_decl.lastToken());
+                // Doc comments for a field may also appear after the comma, eg.
+                // field_name: T, // comment attached to field_name
+                if (tree.tokens.at(maybe_comma).id == .DocComment)
+                    maybe_comma = tree.prevToken(maybe_comma);
+                break :blk tree.tokens.at(maybe_comma).id == .Comma;
+            };
+
+            // We can only print all the elements in-line if all the
+            // declarations inside are fields
+            const src_has_only_fields = blk: {
+                var it = container_decl.fields_and_decls.iterator(0);
+                while (it.next()) |decl| {
+                    if (decl.*.id != .ContainerField) break :blk false;
+                }
+                break :blk true;
+            };
+
+            if (src_has_trailing_comma or !src_has_only_fields) {
+                // One declaration per line
                 const new_indent = indent + indent_delta;
-                try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, Space.Newline); // {
+                try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // {
 
                 var it = container_decl.fields_and_decls.iterator(0);
                 while (it.next()) |decl| {
                     try stream.writeByteNTimes(' ', new_indent);
-                    try renderTopLevelDecl(allocator, stream, tree, new_indent, start_col, decl.*);
+                    try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl.*, .Newline);
 
                     if (it.peek()) |next_decl| {
                         try renderExtraNewline(tree, stream, start_col, next_decl.*);
@@ -1165,8 +1205,17 @@ fn renderExpression(
                 }
 
                 try stream.writeByteNTimes(' ', indent);
-                return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
+            } else {
+                // All the declarations on the same line
+                try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Space); // {
+
+                var it = container_decl.fields_and_decls.iterator(0);
+                while (it.next()) |decl| {
+                    try renderContainerDecl(allocator, stream, tree, indent, start_col, decl.*, .Space);
+                }
             }
+
+            return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
         },
 
         .ErrorSetDecl => {