Commit 5306b1a9ab

Isaac Freund <ifreund@ifreund.xyz>
2021-02-23 18:32:47
zig fmt: container doc comments
1 parent 1f62e87
lib/std/zig/ast.zig
@@ -525,17 +525,17 @@ pub const Tree = struct {
             },
 
             .container_decl,
-            .container_decl_comma,
+            .container_decl_trailing,
             .container_decl_two,
-            .container_decl_two_comma,
+            .container_decl_two_trailing,
             .container_decl_arg,
-            .container_decl_arg_comma,
+            .container_decl_arg_trailing,
             .tagged_union,
-            .tagged_union_comma,
+            .tagged_union_trailing,
             .tagged_union_two,
-            .tagged_union_two_comma,
+            .tagged_union_two_trailing,
             .tagged_union_enum_tag,
-            .tagged_union_enum_tag_comma,
+            .tagged_union_enum_tag_trailing,
             => {
                 const main_token = main_tokens[n];
                 switch (token_tags[main_token - 1]) {
@@ -606,6 +606,7 @@ pub const Tree = struct {
         const datas = tree.nodes.items(.data);
         const main_tokens = tree.nodes.items(.main_token);
         const token_starts = tree.tokens.items(.start);
+        const token_tags = tree.tokens.items(.tag);
         var n = node;
         var end_offset: TokenIndex = 0;
         while (true) switch (tags[n]) {
@@ -738,9 +739,9 @@ pub const Tree = struct {
             },
             .call_comma,
             .async_call_comma,
-            .tagged_union_enum_tag_comma,
+            .tagged_union_enum_tag_trailing,
             => {
-                end_offset += 2; // for the comma + rparen/rbrace
+                end_offset += 2; // for the comma/semicolon + rparen/rbrace
                 const params = tree.extraData(datas[n].rhs, Node.SubRange);
                 assert(params.end > params.start);
                 n = tree.extra_data[params.end - 1]; // last parameter
@@ -779,7 +780,7 @@ pub const Tree = struct {
             },
             .array_init_comma,
             .struct_init_comma,
-            .container_decl_arg_comma,
+            .container_decl_arg_trailing,
             .switch_comma,
             => {
                 const members = tree.extraData(datas[n].rhs, Node.SubRange);
@@ -801,8 +802,8 @@ pub const Tree = struct {
             .array_init_dot_comma,
             .struct_init_dot_comma,
             .block_semicolon,
-            .container_decl_comma,
-            .tagged_union_comma,
+            .container_decl_trailing,
+            .tagged_union_trailing,
             .builtin_call_comma,
             => {
                 assert(datas[n].rhs - datas[n].lhs > 0);
@@ -838,10 +839,17 @@ pub const Tree = struct {
                         .block_two,
                         .struct_init_dot_two,
                         => end_offset += 1, // rbrace
-                        .builtin_call_two,
-                        .container_decl_two,
-                        => end_offset += 2, // lparen/lbrace + rparen/rbrace
-                        .tagged_union_two => end_offset += 5, // (enum) {}
+                        .builtin_call_two => end_offset += 2, // lparen/lbrace + rparen/rbrace
+                        .container_decl_two => {
+                            var i: u32 = 2; // lbrace + rbrace
+                            while (token_tags[main_tokens[n] + i] == .container_doc_comment) i += 1;
+                            end_offset += i;
+                        },
+                        .tagged_union_two => {
+                            var i: u32 = 5; // (enum) {}
+                            while (token_tags[main_tokens[n] + i] == .container_doc_comment) i += 1;
+                            end_offset += i;
+                        },
                         else => unreachable,
                     }
                     return main_tokens[n] + end_offset;
@@ -851,8 +859,8 @@ pub const Tree = struct {
             .builtin_call_two_comma,
             .block_two_semicolon,
             .struct_init_dot_two_comma,
-            .container_decl_two_comma,
-            .tagged_union_two_comma,
+            .container_decl_two_trailing,
+            .tagged_union_two_trailing,
             => {
                 end_offset += 2; // for the comma/semicolon + rbrace/rparen
                 if (datas[n].rhs != 0) {
@@ -1531,7 +1539,7 @@ pub const Tree = struct {
 
     pub fn containerDeclTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .container_decl_two or
-            tree.nodes.items(.tag)[node] == .container_decl_two_comma);
+            tree.nodes.items(.tag)[node] == .container_decl_two_trailing);
         const data = tree.nodes.items(.data)[node];
         buffer.* = .{ data.lhs, data.rhs };
         const members = if (data.rhs != 0)
@@ -1550,7 +1558,7 @@ pub const Tree = struct {
 
     pub fn containerDecl(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .container_decl or
-            tree.nodes.items(.tag)[node] == .container_decl_comma);
+            tree.nodes.items(.tag)[node] == .container_decl_trailing);
         const data = tree.nodes.items(.data)[node];
         return tree.fullContainerDecl(.{
             .main_token = tree.nodes.items(.main_token)[node],
@@ -1562,7 +1570,7 @@ pub const Tree = struct {
 
     pub fn containerDeclArg(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .container_decl_arg or
-            tree.nodes.items(.tag)[node] == .container_decl_arg_comma);
+            tree.nodes.items(.tag)[node] == .container_decl_arg_trailing);
         const data = tree.nodes.items(.data)[node];
         const members_range = tree.extraData(data.rhs, Node.SubRange);
         return tree.fullContainerDecl(.{
@@ -1575,7 +1583,7 @@ pub const Tree = struct {
 
     pub fn taggedUnionTwo(tree: Tree, buffer: *[2]Node.Index, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .tagged_union_two or
-            tree.nodes.items(.tag)[node] == .tagged_union_two_comma);
+            tree.nodes.items(.tag)[node] == .tagged_union_two_trailing);
         const data = tree.nodes.items(.data)[node];
         buffer.* = .{ data.lhs, data.rhs };
         const members = if (data.rhs != 0)
@@ -1595,7 +1603,7 @@ pub const Tree = struct {
 
     pub fn taggedUnion(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .tagged_union or
-            tree.nodes.items(.tag)[node] == .tagged_union_comma);
+            tree.nodes.items(.tag)[node] == .tagged_union_trailing);
         const data = tree.nodes.items(.data)[node];
         const main_token = tree.nodes.items(.main_token)[node];
         return tree.fullContainerDecl(.{
@@ -1608,7 +1616,7 @@ pub const Tree = struct {
 
     pub fn taggedUnionEnumTag(tree: Tree, node: Node.Index) full.ContainerDecl {
         assert(tree.nodes.items(.tag)[node] == .tagged_union_enum_tag or
-            tree.nodes.items(.tag)[node] == .tagged_union_enum_tag_comma);
+            tree.nodes.items(.tag)[node] == .tagged_union_enum_tag_trailing);
         const data = tree.nodes.items(.data)[node];
         const members_range = tree.extraData(data.rhs, Node.SubRange);
         const main_token = tree.nodes.items(.main_token)[node];
@@ -2762,36 +2770,40 @@ pub const Node = struct {
         /// `struct {}`, `union {}`, `opaque {}`, `enum {}`. `extra_data[lhs..rhs]`.
         /// main_token is `struct`, `union`, `opaque`, `enum` keyword.
         container_decl,
-        /// Same as ContainerDecl but there is known to be a trailing comma before the rbrace.
-        container_decl_comma,
+        /// Same as ContainerDecl but there is known to be a trailing comma
+        /// or semicolon before the rbrace.
+        container_decl_trailing,
         /// `struct {lhs, rhs}`, `union {lhs, rhs}`, `opaque {lhs, rhs}`, `enum {lhs, rhs}`.
         /// lhs or rhs can be omitted.
         /// main_token is `struct`, `union`, `opaque`, `enum` keyword.
         container_decl_two,
         /// Same as ContainerDeclTwo except there is known to be a trailing comma
-        /// before the rbrace.
-        container_decl_two_comma,
+        /// or semicolon before the rbrace.
+        container_decl_two_trailing,
         /// `union(lhs)` / `enum(lhs)`. `SubRange[rhs]`.
         container_decl_arg,
-        /// Same as container_decl_arg but there is known to be a trailing comma before the rbrace.
-        container_decl_arg_comma,
+        /// Same as container_decl_arg but there is known to be a trailing
+        /// comma or semicolon before the rbrace.
+        container_decl_arg_trailing,
         /// `union(enum) {}`. `sub_list[lhs..rhs]`.
         /// Note that tagged unions with explicitly provided enums are represented
         /// by `container_decl_arg`.
         tagged_union,
-        /// Same as tagged_union but there is known to be a trailing comma before the rbrace.
-        tagged_union_comma,
+        /// Same as tagged_union but there is known to be a trailing comma
+        /// or semicolon before the rbrace.
+        tagged_union_trailing,
         /// `union(enum) {lhs, rhs}`. lhs or rhs may be omitted.
         /// Note that tagged unions with explicitly provided enums are represented
         /// by `container_decl_arg`.
         tagged_union_two,
-        /// Same as tagged_union_two but there is known to be a trailing comma before the rbrace.
-        tagged_union_two_comma,
+        /// Same as tagged_union_two but there is known to be a trailing comma
+        /// or semicolon before the rbrace.
+        tagged_union_two_trailing,
         /// `union(enum(lhs)) {}`. `SubRange[rhs]`.
         tagged_union_enum_tag,
         /// Same as tagged_union_enum_tag but there is known to be a trailing comma
-        /// before the rbrace.
-        tagged_union_enum_tag_comma,
+        /// or semicolon before the rbrace.
+        tagged_union_enum_tag_trailing,
         /// `a: lhs = rhs,`. lhs and rhs can be omitted.
         /// main_token is the field name identifier.
         /// lastToken() does not include the possible trailing comma.
lib/std/zig/parse.zig
@@ -113,7 +113,7 @@ const Parser = struct {
         len: usize,
         lhs: Node.Index,
         rhs: Node.Index,
-        trailing_comma: bool,
+        trailing: bool,
 
         fn toSpan(self: Members, p: *Parser) !Node.SubRange {
             if (self.len <= 2) {
@@ -215,7 +215,7 @@ const Parser = struct {
         // Skip container doc comments.
         while (p.eatToken(.container_doc_comment)) |_| {}
 
-        var trailing_comma = false;
+        var trailing = false;
         while (true) {
             const doc_comment = try p.eatDocComments();
 
@@ -228,7 +228,7 @@ const Parser = struct {
                         }
                         try list.append(test_decl_node);
                     }
-                    trailing_comma = false;
+                    trailing = false;
                 },
                 .keyword_comptime => switch (p.token_tags[p.tok_i + 1]) {
                     .identifier => {
@@ -251,11 +251,11 @@ const Parser = struct {
                             switch (p.token_tags[p.tok_i]) {
                                 .comma => {
                                     p.tok_i += 1;
-                                    trailing_comma = true;
+                                    trailing = true;
                                     continue;
                                 },
                                 .r_brace, .eof => {
-                                    trailing_comma = false;
+                                    trailing = false;
                                     break;
                                 },
                                 else => {},
@@ -289,7 +289,7 @@ const Parser = struct {
                             }
                             try list.append(comptime_node);
                         }
-                        trailing_comma = false;
+                        trailing = false;
                     },
                     else => {
                         p.tok_i += 1;
@@ -305,7 +305,7 @@ const Parser = struct {
                         }
                         try list.append(top_level_decl);
                     }
-                    trailing_comma = false;
+                    trailing = p.token_tags[p.tok_i - 1] == .semicolon;
                 },
                 .keyword_usingnamespace => {
                     const node = try p.expectUsingNamespaceRecoverable();
@@ -315,7 +315,7 @@ const Parser = struct {
                         }
                         try list.append(node);
                     }
-                    trailing_comma = false;
+                    trailing = p.token_tags[p.tok_i - 1] == .semicolon;
                 },
                 .keyword_const,
                 .keyword_var,
@@ -333,7 +333,7 @@ const Parser = struct {
                         }
                         try list.append(top_level_decl);
                     }
-                    trailing_comma = false;
+                    trailing = p.token_tags[p.tok_i - 1] == .semicolon;
                 },
                 .identifier => {
                     const container_field = try p.expectContainerFieldRecoverable();
@@ -354,11 +354,11 @@ const Parser = struct {
                         switch (p.token_tags[p.tok_i]) {
                             .comma => {
                                 p.tok_i += 1;
-                                trailing_comma = true;
+                                trailing = true;
                                 continue;
                             },
                             .r_brace, .eof => {
-                                trailing_comma = false;
+                                trailing = false;
                                 break;
                             },
                             else => {},
@@ -391,19 +391,19 @@ const Parser = struct {
                 .len = 0,
                 .lhs = 0,
                 .rhs = 0,
-                .trailing_comma = trailing_comma,
+                .trailing = trailing,
             },
             1 => return Members{
                 .len = 1,
                 .lhs = list.items[0],
                 .rhs = 0,
-                .trailing_comma = trailing_comma,
+                .trailing = trailing,
             },
             2 => return Members{
                 .len = 2,
                 .lhs = list.items[0],
                 .rhs = list.items[1],
-                .trailing_comma = trailing_comma,
+                .trailing = trailing,
             },
             else => {
                 const span = try p.listToSpan(list.items);
@@ -411,7 +411,7 @@ const Parser = struct {
                     .len = list.items.len,
                     .lhs = span.start,
                     .rhs = span.end,
-                    .trailing_comma = trailing_comma,
+                    .trailing = trailing,
                 };
             },
         }
@@ -3575,8 +3575,8 @@ const Parser = struct {
                             const members_span = try members.toSpan(p);
                             _ = try p.expectToken(.r_brace);
                             return p.addNode(.{
-                                .tag = switch (members.trailing_comma) {
-                                    true => .tagged_union_enum_tag_comma,
+                                .tag = switch (members.trailing) {
+                                    true => .tagged_union_enum_tag_trailing,
                                     false => .tagged_union_enum_tag,
                                 },
                                 .main_token = main_token,
@@ -3593,8 +3593,8 @@ const Parser = struct {
                             _ = try p.expectToken(.r_brace);
                             if (members.len <= 2) {
                                 return p.addNode(.{
-                                    .tag = switch (members.trailing_comma) {
-                                        true => .tagged_union_two_comma,
+                                    .tag = switch (members.trailing) {
+                                        true => .tagged_union_two_trailing,
                                         false => .tagged_union_two,
                                     },
                                     .main_token = main_token,
@@ -3606,8 +3606,8 @@ const Parser = struct {
                             } else {
                                 const span = try members.toSpan(p);
                                 return p.addNode(.{
-                                    .tag = switch (members.trailing_comma) {
-                                        true => .tagged_union_comma,
+                                    .tag = switch (members.trailing) {
+                                        true => .tagged_union_trailing,
                                         false => .tagged_union,
                                     },
                                     .main_token = main_token,
@@ -3638,8 +3638,8 @@ const Parser = struct {
         if (arg_expr == 0) {
             if (members.len <= 2) {
                 return p.addNode(.{
-                    .tag = switch (members.trailing_comma) {
-                        true => .container_decl_two_comma,
+                    .tag = switch (members.trailing) {
+                        true => .container_decl_two_trailing,
                         false => .container_decl_two,
                     },
                     .main_token = main_token,
@@ -3651,8 +3651,8 @@ const Parser = struct {
             } else {
                 const span = try members.toSpan(p);
                 return p.addNode(.{
-                    .tag = switch (members.trailing_comma) {
-                        true => .container_decl_comma,
+                    .tag = switch (members.trailing) {
+                        true => .container_decl_trailing,
                         false => .container_decl,
                     },
                     .main_token = main_token,
@@ -3665,8 +3665,8 @@ const Parser = struct {
         } else {
             const span = try members.toSpan(p);
             return p.addNode(.{
-                .tag = switch (members.trailing_comma) {
-                    true => .container_decl_arg_comma,
+                .tag = switch (members.trailing) {
+                    true => .container_decl_arg_trailing,
                     false => .container_decl_arg,
                 },
                 .main_token = main_token,
lib/std/zig/parser_test.zig
@@ -3577,61 +3577,61 @@ test "zig fmt: file ends with struct field" {
 //    );
 //}
 
-//test "zig fmt: top level doc comments" {
-//    try testCanonical(
-//        \\//! tld 1
-//        \\//! tld 2
-//        \\//! tld 3
-//        \\
-//        \\// comment
-//        \\
-//        \\/// A doc
-//        \\const A = struct {
-//        \\    //! A tld 1
-//        \\    //! A tld 2
-//        \\    //! A tld 3
-//        \\};
-//        \\
-//        \\/// B doc
-//        \\const B = struct {
-//        \\    //! B tld 1
-//        \\    //! B tld 2
-//        \\    //! B tld 3
-//        \\
-//        \\    /// b doc
-//        \\    b: u32,
-//        \\};
-//        \\
-//        \\/// C doc
-//        \\const C = struct {
-//        \\    //! C tld 1
-//        \\    //! C tld 2
-//        \\    //! C tld 3
-//        \\
-//        \\    /// c1 doc
-//        \\    c1: u32,
-//        \\
-//        \\    //! C tld 4
-//        \\    //! C tld 5
-//        \\    //! C tld 6
-//        \\
-//        \\    /// c2 doc
-//        \\    c2: u32,
-//        \\};
-//        \\
-//    );
-//    try testCanonical(
-//        \\//! Top-level documentation.
-//        \\
-//        \\/// This is A
-//        \\pub const A = usize;
-//        \\
-//    );
-//    try testCanonical(
-//        \\//! Nothing here
-//        \\
-//    );
-//}
+test "zig fmt: container doc comments" {
+    try testCanonical(
+        \\//! tld 1
+        \\//! tld 2
+        \\//! tld 3
+        \\
+        \\// comment
+        \\
+        \\/// A doc
+        \\const A = struct {
+        \\    //! A tld 1
+        \\    //! A tld 2
+        \\    //! A tld 3
+        \\};
+        \\
+        \\/// B doc
+        \\const B = struct {
+        \\    //! B tld 1
+        \\    //! B tld 2
+        \\    //! B tld 3
+        \\
+        \\    /// B doc
+        \\    b: u32,
+        \\};
+        \\
+        \\/// C doc
+        \\const C = union(enum) { // comment
+        \\    //! C tld 1
+        \\    //! C tld 2
+        \\    //! C tld 3
+        \\};
+        \\
+        \\/// D doc
+        \\const D = union(Foo) {
+        \\    //! D tld 1
+        \\    //! D tld 2
+        \\    //! D tld 3
+        \\
+        \\    /// D doc
+        \\    b: u32,
+        \\};
+        \\
+    );
+    try testCanonical(
+        \\//! Top-level documentation.
+        \\
+        \\/// This is A
+        \\pub const A = usize;
+        \\
+    );
+    try testCanonical(
+        \\//! Nothing here
+        \\
+    );
+}
 
 test "zig fmt: extern without container keyword returns error" {
     try testError(
lib/std/zig/render.zig
@@ -30,6 +30,10 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: ast.Tree) Error!void {
     const comment_end_loc = tree.tokens.items(.start)[0];
     _ = try renderComments(ais, tree, 0, comment_end_loc);
 
+    if (tree.tokens.items(.tag)[0] == .container_doc_comment) {
+        try renderContainerDocComments(ais, tree, 0);
+    }
+
     try renderMembers(buffer.allocator, ais, tree, tree.rootDecls());
 
     if (ais.disabled_offset) |disabled_offset| {
@@ -506,28 +510,28 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.I
         },
 
         .container_decl,
-        .container_decl_comma,
-        => return renderContainerDecl(gpa, ais, tree, tree.containerDecl(node), space),
+        .container_decl_trailing,
+        => return renderContainerDecl(gpa, ais, tree, node, tree.containerDecl(node), space),
 
-        .container_decl_two, .container_decl_two_comma => {
+        .container_decl_two, .container_decl_two_trailing => {
             var buffer: [2]ast.Node.Index = undefined;
-            return renderContainerDecl(gpa, ais, tree, tree.containerDeclTwo(&buffer, node), space);
+            return renderContainerDecl(gpa, ais, tree, node, tree.containerDeclTwo(&buffer, node), space);
         },
         .container_decl_arg,
-        .container_decl_arg_comma,
-        => return renderContainerDecl(gpa, ais, tree, tree.containerDeclArg(node), space),
+        .container_decl_arg_trailing,
+        => return renderContainerDecl(gpa, ais, tree, node, tree.containerDeclArg(node), space),
 
         .tagged_union,
-        .tagged_union_comma,
-        => return renderContainerDecl(gpa, ais, tree, tree.taggedUnion(node), space),
+        .tagged_union_trailing,
+        => return renderContainerDecl(gpa, ais, tree, node, tree.taggedUnion(node), space),
 
-        .tagged_union_two, .tagged_union_two_comma => {
+        .tagged_union_two, .tagged_union_two_trailing => {
             var buffer: [2]ast.Node.Index = undefined;
-            return renderContainerDecl(gpa, ais, tree, tree.taggedUnionTwo(&buffer, node), space);
+            return renderContainerDecl(gpa, ais, tree, node, tree.taggedUnionTwo(&buffer, node), space);
         },
         .tagged_union_enum_tag,
-        .tagged_union_enum_tag_comma,
-        => return renderContainerDecl(gpa, ais, tree, tree.taggedUnionEnumTag(node), space),
+        .tagged_union_enum_tag_trailing,
+        => return renderContainerDecl(gpa, ais, tree, node, tree.taggedUnionEnumTag(node), space),
 
         .error_set_decl => {
             const error_token = main_tokens[node];
@@ -1662,7 +1666,6 @@ fn renderArrayInit(
     ais.pushIndentNextLine();
     try renderToken(ais, tree, array_init.ast.lbrace, .newline);
 
-
     var expr_index: usize = 0;
     while (rowSize(tree, array_init.ast.elements[expr_index..], rbrace)) |row_size| {
         const row_exprs = array_init.ast.elements[expr_index..];
@@ -1806,6 +1809,7 @@ fn renderContainerDecl(
     gpa: *Allocator,
     ais: *Ais,
     tree: ast.Tree,
+    container_decl_node: ast.Node.Index,
     container_decl: ast.full.ContainerDecl,
     space: Space,
 ) Error!void {
@@ -1844,25 +1848,20 @@ fn renderContainerDecl(
         lbrace = container_decl.ast.main_token + 1;
     }
 
+    const rbrace = tree.lastToken(container_decl_node);
     if (container_decl.ast.members.len == 0) {
-        try renderToken(ais, tree, lbrace, Space.none); // lbrace
-        return renderToken(ais, tree, lbrace + 1, space); // rbrace
+        ais.pushIndentNextLine();
+        if (token_tags[lbrace + 1] == .container_doc_comment) {
+            try renderToken(ais, tree, lbrace, .newline); // lbrace
+            try renderContainerDocComments(ais, tree, lbrace + 1);
+        } else {
+            try renderToken(ais, tree, lbrace, .none); // lbrace
+        }
+        ais.popIndent();
+        return renderToken(ais, tree, rbrace, space); // rbrace
     }
 
-    const last_member = container_decl.ast.members[container_decl.ast.members.len - 1];
-    const last_member_token = tree.lastToken(last_member);
-    const rbrace = switch (token_tags[last_member_token + 1]) {
-        .doc_comment => last_member_token + 2,
-        .comma, .semicolon => switch (token_tags[last_member_token + 2]) {
-            .doc_comment => last_member_token + 3,
-            .r_brace => last_member_token + 2,
-            else => unreachable,
-        },
-        .r_brace => last_member_token + 1,
-        else => unreachable,
-    };
-    const src_has_trailing_comma = token_tags[last_member_token + 1] == .comma;
-
+    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.
         for (container_decl.ast.members) |member| {
@@ -1879,6 +1878,9 @@ fn renderContainerDecl(
     // One member per line.
     ais.pushIndentNextLine();
     try renderToken(ais, tree, lbrace, .newline); // lbrace
+    if (token_tags[lbrace + 1] == .container_doc_comment) {
+        try renderContainerDocComments(ais, tree, lbrace + 1);
+    }
     try renderMembers(gpa, ais, tree, container_decl.ast.members);
     ais.popIndent();
 
@@ -2272,6 +2274,15 @@ fn renderDocComments(ais: *Ais, tree: ast.Tree, end_token: ast.TokenIndex) Error
     }
 }
 
+/// start_token is first container doc comment token.
+fn renderContainerDocComments(ais: *Ais, tree: ast.Tree, start_token: ast.TokenIndex) Error!void {
+    const token_tags = tree.tokens.items(.tag);
+    var tok = start_token;
+    while (token_tags[tok] == .container_doc_comment) : (tok += 1) {
+        try renderToken(ais, tree, tok, .newline);
+    }
+}
+
 fn tokenSliceForRender(tree: ast.Tree, token_index: ast.TokenIndex) []const u8 {
     var ret = tree.tokenSlice(token_index);
     if (tree.tokens.items(.tag)[token_index] == .multiline_string_literal_line) {
src/astgen.zig
@@ -215,17 +215,17 @@ fn lvalExpr(mod: *Module, scope: *Scope, node: ast.Node.Index) InnerError!*zir.I
         .anyframe_literal,
         .error_set_decl,
         .container_decl,
-        .container_decl_comma,
+        .container_decl_trailing,
         .container_decl_two,
-        .container_decl_two_comma,
+        .container_decl_two_trailing,
         .container_decl_arg,
-        .container_decl_arg_comma,
+        .container_decl_arg_trailing,
         .tagged_union,
-        .tagged_union_comma,
+        .tagged_union_trailing,
         .tagged_union_two,
-        .tagged_union_two_comma,
+        .tagged_union_two_trailing,
         .tagged_union_enum_tag,
-        .tagged_union_enum_tag_comma,
+        .tagged_union_enum_tag_trailing,
         .@"comptime",
         .@"nosuspend",
         .error_value,
@@ -577,25 +577,25 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: ast.Node.Index) In
         .ptr_type_bit_range => return ptrType(mod, scope, rl, tree.ptrTypeBitRange(node)),
 
         .container_decl,
-        .container_decl_comma,
+        .container_decl_trailing,
         => return containerDecl(mod, scope, rl, tree.containerDecl(node)),
-        .container_decl_two, .container_decl_two_comma => {
+        .container_decl_two, .container_decl_two_trailing => {
             var buffer: [2]ast.Node.Index = undefined;
             return containerDecl(mod, scope, rl, tree.containerDeclTwo(&buffer, node));
         },
         .container_decl_arg,
-        .container_decl_arg_comma,
+        .container_decl_arg_trailing,
         => return containerDecl(mod, scope, rl, tree.containerDeclArg(node)),
 
         .tagged_union,
-        .tagged_union_comma,
+        .tagged_union_trailing,
         => return containerDecl(mod, scope, rl, tree.taggedUnion(node)),
-        .tagged_union_two, .tagged_union_two_comma => {
+        .tagged_union_two, .tagged_union_two_trailing => {
             var buffer: [2]ast.Node.Index = undefined;
             return containerDecl(mod, scope, rl, tree.taggedUnionTwo(&buffer, node));
         },
         .tagged_union_enum_tag,
-        .tagged_union_enum_tag_comma,
+        .tagged_union_enum_tag_trailing,
         => return containerDecl(mod, scope, rl, tree.taggedUnionEnumTag(node)),
 
         .@"break" => return breakExpr(mod, scope, rl, node),
@@ -3715,17 +3715,17 @@ fn nodeMayNeedMemoryLocation(scope: *Scope, start_node: ast.Node.Index) bool {
             .identifier,
             .error_set_decl,
             .container_decl,
-            .container_decl_comma,
+            .container_decl_trailing,
             .container_decl_two,
-            .container_decl_two_comma,
+            .container_decl_two_trailing,
             .container_decl_arg,
-            .container_decl_arg_comma,
+            .container_decl_arg_trailing,
             .tagged_union,
-            .tagged_union_comma,
+            .tagged_union_trailing,
             .tagged_union_two,
-            .tagged_union_two_comma,
+            .tagged_union_two_trailing,
             .tagged_union_enum_tag,
-            .tagged_union_enum_tag_comma,
+            .tagged_union_enum_tag_trailing,
             .@"asm",
             .asm_simple,
             .add,