authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-02-01 11:28:21+01:00
committergravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-02-01 11:28:21+01:00
log8309ee875236b9fd2fb80482010464e218173c4c
tree93323ffaebb6548b279a069e299c78efcb0f7427
parente6a812c8279defd192fa78f031eb952b9b78a7ed

fmt: Respect trailing comma for field declarations

Closes #4349

3 files changed, 114 insertions(+), 52 deletions(-)

lib/std/zig/ast.zig+11
......@@ -10,6 +10,7 @@ pub const TokenIndex = usize;
1010pub const Tree = struct {
1111 source: []const u8,
1212 tokens: TokenList,
13
1314 /// undefined on parse error (errors not empty)
1415 root_node: *Node.Root,
1516 arena_allocator: std.heap.ArenaAllocator,
......@@ -780,6 +781,11 @@ pub const Node = struct {
780781 i -= 1;
781782 }
782783
784 if (self.align_expr) |align_expr| {
785 if (i < 1) return align_expr;
786 i -= 1;
787 }
788
783789 if (self.value_expr) |value_expr| {
784790 if (i < 1) return value_expr;
785791 i -= 1;
......@@ -796,6 +802,11 @@ pub const Node = struct {
796802 if (self.value_expr) |value_expr| {
797803 return value_expr.lastToken();
798804 }
805 if (self.align_expr) |align_expr| {
806 // The expression refers to what's inside the parenthesis, the
807 // last token is the closing one
808 return align_expr.lastToken() + 1;
809 }
799810 if (self.type_expr) |type_expr| {
800811 return type_expr.lastToken();
801812 }
lib/std/zig/parser_test.zig+14-12
......@@ -1,3 +1,14 @@
1test "zig fmt: trailing comma in container declaration" {
2 try testCanonical(
3 \\const X = struct { foo: i32 };
4 \\const X = struct { foo: i32, bar: i32 };
5 \\const X = struct { foo: i32 = 1, bar: i32 = 2 };
6 \\const X = struct { foo: i32 align(4), bar: i32 align(4) };
7 \\const X = struct { foo: i32 align(4) = 1, bar: i32 align(4) = 2 };
8 \\
9 );
10}
11
112test "zig fmt: trailing comma in fn parameter list" {
213 try testCanonical(
314 \\pub fn f(
......@@ -727,10 +738,7 @@ test "zig fmt: enum decl with no trailing comma" {
727738 try testTransform(
728739 \\const StrLitKind = enum {Normal, C};
729740 ,
730 \\const StrLitKind = enum {
731 \\ Normal,
732 \\ C,
733 \\};
741 \\const StrLitKind = enum { Normal, C };
734742 \\
735743 );
736744}
......@@ -989,11 +997,7 @@ test "zig fmt: no trailing comma on struct decl" {
989997 \\ k: usize, s: u32, t: u32
990998 \\};
991999 ,
992 \\const RoundParam = struct {
993 \\ k: usize,
994 \\ s: u32,
995 \\ t: u32,
996 \\};
1000 \\const RoundParam = struct { k: usize, s: u32, t: u32 };
9971001 \\
9981002 );
9991003}
......@@ -2560,10 +2564,8 @@ test "zig fmt: if type expr" {
25602564 );
25612565}
25622566test "zig fmt: file ends with struct field" {
2563 try testTransform(
2567 try testCanonical(
25642568 \\a: bool
2565 ,
2566 \\a: bool,
25672569 \\
25682570 );
25692571}
lib/std/zig/render.zig+89-40
......@@ -206,6 +206,10 @@ fn renderExtraNewline(tree: *ast.Tree, stream: var, start_col: *usize, node: *as
206206}
207207
208208fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, indent: usize, start_col: *usize, decl: *ast.Node) (@TypeOf(stream).Child.Error || Error)!void {
209 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl, .Newline);
210}
211
212fn 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 {
209213 switch (decl.id) {
210214 .FnProto => {
211215 const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
......@@ -213,11 +217,11 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
213217 try renderDocComments(tree, stream, fn_proto, indent, start_col);
214218
215219 if (fn_proto.body_node) |body_node| {
216 try renderExpression(allocator, stream, tree, indent, start_col, decl, Space.Space);
217 try renderExpression(allocator, stream, tree, indent, start_col, body_node, Space.Newline);
220 try renderExpression(allocator, stream, tree, indent, start_col, decl, .Space);
221 try renderExpression(allocator, stream, tree, indent, start_col, body_node, space);
218222 } else {
219 try renderExpression(allocator, stream, tree, indent, start_col, decl, Space.None);
220 try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, Space.Newline);
223 try renderExpression(allocator, stream, tree, indent, start_col, decl, .None);
224 try renderToken(tree, stream, tree.nextToken(decl.lastToken()), indent, start_col, space);
221225 }
222226 },
223227
......@@ -225,11 +229,11 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
225229 const use_decl = @fieldParentPtr(ast.Node.Use, "base", decl);
226230
227231 if (use_decl.visib_token) |visib_token| {
228 try renderToken(tree, stream, visib_token, indent, start_col, Space.Space); // pub
232 try renderToken(tree, stream, visib_token, indent, start_col, .Space); // pub
229233 }
230 try renderToken(tree, stream, use_decl.use_token, indent, start_col, Space.Space); // usingnamespace
231 try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, Space.None);
232 try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, Space.Newline); // ;
234 try renderToken(tree, stream, use_decl.use_token, indent, start_col, .Space); // usingnamespace
235 try renderExpression(allocator, stream, tree, indent, start_col, use_decl.expr, .None);
236 try renderToken(tree, stream, use_decl.semicolon_token, indent, start_col, space); // ;
233237 },
234238
235239 .VarDecl => {
......@@ -243,9 +247,9 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
243247 const test_decl = @fieldParentPtr(ast.Node.TestDecl, "base", decl);
244248
245249 try renderDocComments(tree, stream, test_decl, indent, start_col);
246 try renderToken(tree, stream, test_decl.test_token, indent, start_col, Space.Space);
247 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, Space.Space);
248 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, Space.Newline);
250 try renderToken(tree, stream, test_decl.test_token, indent, start_col, .Space);
251 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.name, .Space);
252 try renderExpression(allocator, stream, tree, indent, start_col, test_decl.body_node, space);
249253 },
250254
251255 .ContainerField => {
......@@ -253,62 +257,76 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
253257
254258 try renderDocComments(tree, stream, field, indent, start_col);
255259 if (field.comptime_token) |t| {
256 try renderToken(tree, stream, t, indent, start_col, Space.Space); // comptime
260 try renderToken(tree, stream, t, indent, start_col, .Space); // comptime
257261 }
258262
263 const src_has_trailing_comma = blk: {
264 const maybe_comma = tree.nextToken(field.lastToken());
265 break :blk tree.tokens.at(maybe_comma).id == .Comma;
266 };
267
268 // The trailing comma is emitted at the end, but if it's not present
269 // we still have to respect the specified `space` parameter
270 const last_token_space: Space = if (src_has_trailing_comma) .None else space;
271
259272 if (field.type_expr == null and field.value_expr == null) {
260 return renderToken(tree, stream, field.name_token, indent, start_col, Space.Comma); // name,
273 try renderToken(tree, stream, field.name_token, indent, start_col, last_token_space); // name
261274 } else if (field.type_expr != null and field.value_expr == null) {
262 try renderToken(tree, stream, field.name_token, indent, start_col, Space.None); // name
263 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, Space.Space); // :
275 try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name
276 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :
264277
265278 if (field.align_expr) |align_value_expr| {
266 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Space); // type
279 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
267280 const lparen_token = tree.prevToken(align_value_expr.firstToken());
268281 const align_kw = tree.prevToken(lparen_token);
269282 const rparen_token = tree.nextToken(align_value_expr.lastToken());
270 try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
271 try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
272 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, Space.None); // alignment
273 try renderToken(tree, stream, rparen_token, indent, start_col, Space.Comma); // ),
283 try renderToken(tree, stream, align_kw, indent, start_col, .None); // align
284 try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (
285 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment
286 try renderToken(tree, stream, rparen_token, indent, start_col, last_token_space); // )
274287 } else {
275 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Comma); // type,
288 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, last_token_space); // type
276289 }
277290 } else if (field.type_expr == null and field.value_expr != null) {
278 try renderToken(tree, stream, field.name_token, indent, start_col, Space.Space); // name
279 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, Space.Space); // =
280 return renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, Space.Comma); // value
291 try renderToken(tree, stream, field.name_token, indent, start_col, .Space); // name
292 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // =
293 try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value
281294 } else {
282 try renderToken(tree, stream, field.name_token, indent, start_col, Space.None); // name
283 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, Space.Space); // :
295 try renderToken(tree, stream, field.name_token, indent, start_col, .None); // name
296 try renderToken(tree, stream, tree.nextToken(field.name_token), indent, start_col, .Space); // :
284297
285298 if (field.align_expr) |align_value_expr| {
286 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Space); // type
299 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
287300 const lparen_token = tree.prevToken(align_value_expr.firstToken());
288301 const align_kw = tree.prevToken(lparen_token);
289302 const rparen_token = tree.nextToken(align_value_expr.lastToken());
290 try renderToken(tree, stream, align_kw, indent, start_col, Space.None); // align
291 try renderToken(tree, stream, lparen_token, indent, start_col, Space.None); // (
292 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, Space.None); // alignment
293 try renderToken(tree, stream, rparen_token, indent, start_col, Space.Space); // )
303 try renderToken(tree, stream, align_kw, indent, start_col, .None); // align
304 try renderToken(tree, stream, lparen_token, indent, start_col, .None); // (
305 try renderExpression(allocator, stream, tree, indent, start_col, align_value_expr, .None); // alignment
306 try renderToken(tree, stream, rparen_token, indent, start_col, .Space); // )
294307 } else {
295 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, Space.Space); // type
308 try renderExpression(allocator, stream, tree, indent, start_col, field.type_expr.?, .Space); // type
296309 }
297 try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, Space.Space); // =
298 return renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, Space.Comma); // value,
310 try renderToken(tree, stream, tree.prevToken(field.value_expr.?.firstToken()), indent, start_col, .Space); // =
311 try renderExpression(allocator, stream, tree, indent, start_col, field.value_expr.?, last_token_space); // value
312 }
313
314 if (src_has_trailing_comma) {
315 const comma = tree.nextToken(field.lastToken());
316 try renderToken(tree, stream, comma, indent, start_col, space);
299317 }
300318 },
301319
302320 .Comptime => {
303321 assert(!decl.requireSemiColon());
304 try renderExpression(allocator, stream, tree, indent, start_col, decl, Space.Newline);
322 try renderExpression(allocator, stream, tree, indent, start_col, decl, space);
305323 },
306324
307325 .DocComment => {
308326 const comment = @fieldParentPtr(ast.Node.DocComment, "base", decl);
309327 var it = comment.lines.iterator(0);
310328 while (it.next()) |line_token_index| {
311 try renderToken(tree, stream, line_token_index.*, indent, start_col, Space.Newline);
329 try renderToken(tree, stream, line_token_index.*, indent, start_col, .Newline);
312330 if (it.peek()) |_| {
313331 try stream.writeByteNTimes(' ', indent);
314332 }
......@@ -1150,14 +1168,36 @@ fn renderExpression(
11501168 if (container_decl.fields_and_decls.len == 0) {
11511169 try renderToken(tree, stream, container_decl.lbrace_token, indent + indent_delta, start_col, Space.None); // {
11521170 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
1153 } else {
1171 }
1172
1173 const src_has_trailing_comma = blk: {
1174 var maybe_comma = tree.prevToken(container_decl.lastToken());
1175 // Doc comments for a field may also appear after the comma, eg.
1176 // field_name: T, // comment attached to field_name
1177 if (tree.tokens.at(maybe_comma).id == .DocComment)
1178 maybe_comma = tree.prevToken(maybe_comma);
1179 break :blk tree.tokens.at(maybe_comma).id == .Comma;
1180 };
1181
1182 // We can only print all the elements in-line if all the
1183 // declarations inside are fields
1184 const src_has_only_fields = blk: {
1185 var it = container_decl.fields_and_decls.iterator(0);
1186 while (it.next()) |decl| {
1187 if (decl.*.id != .ContainerField) break :blk false;
1188 }
1189 break :blk true;
1190 };
1191
1192 if (src_has_trailing_comma or !src_has_only_fields) {
1193 // One declaration per line
11541194 const new_indent = indent + indent_delta;
1155 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, Space.Newline); // {
1195 try renderToken(tree, stream, container_decl.lbrace_token, new_indent, start_col, .Newline); // {
11561196
11571197 var it = container_decl.fields_and_decls.iterator(0);
11581198 while (it.next()) |decl| {
11591199 try stream.writeByteNTimes(' ', new_indent);
1160 try renderTopLevelDecl(allocator, stream, tree, new_indent, start_col, decl.*);
1200 try renderContainerDecl(allocator, stream, tree, new_indent, start_col, decl.*, .Newline);
11611201
11621202 if (it.peek()) |next_decl| {
11631203 try renderExtraNewline(tree, stream, start_col, next_decl.*);
......@@ -1165,8 +1205,17 @@ fn renderExpression(
11651205 }
11661206
11671207 try stream.writeByteNTimes(' ', indent);
1168 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
1208 } else {
1209 // All the declarations on the same line
1210 try renderToken(tree, stream, container_decl.lbrace_token, indent, start_col, .Space); // {
1211
1212 var it = container_decl.fields_and_decls.iterator(0);
1213 while (it.next()) |decl| {
1214 try renderContainerDecl(allocator, stream, tree, indent, start_col, decl.*, .Space);
1215 }
11691216 }
1217
1218 return renderToken(tree, stream, container_decl.rbrace_token, indent, start_col, space); // }
11701219 },
11711220
11721221 .ErrorSetDecl => {