diff --git a/lib/std/zig/ast.zig b/lib/std/zig/ast.zig index 404e8c413a9b..d8943adde030 100644 --- a/lib/std/zig/ast.zig +++ b/lib/std/zig/ast.zig @@ -823,6 +823,15 @@ pub const Node = struct { } } + pub fn findFirstWithId(self: *Node, id: Id) ?*Node { + if (self.id == id) return self; + var child_i: usize = 0; + while (self.iterate(child_i)) |child| : (child_i += 1) { + if (child.findFirstWithId(id)) |result| return result; + } + return null; + } + pub fn dump(self: *Node, indent: usize) void { { var i: usize = 0; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 36ceb400dc47..994ad6d5d1d3 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1301,8 +1301,10 @@ test "zig fmt: array literal with hint" { \\const a = []u8{ \\ 1, 2, \\ 3, 4, - \\ 5, 6, // blah - \\ 7, 8, + \\ 5, + \\ 6, // blah + \\ 7, + \\ 8, \\}; \\const a = []u8{ \\ 1, 2, @@ -1372,7 +1374,7 @@ test "zig fmt: multiline string parameter in fn call with trailing comma" { \\ \\ZIG_C_HEADER_FILES {} \\ \\ZIG_DIA_GUIDS_LIB {} \\ \\ - \\ , + \\ , \\ std.cstr.toSliceConst(c.ZIG_CMAKE_BINARY_DIR), \\ std.cstr.toSliceConst(c.ZIG_CXX_COMPILER), \\ std.cstr.toSliceConst(c.ZIG_DIA_GUIDS_LIB), @@ -3321,6 +3323,326 @@ test "zig fmt: Don't add extra newline after if" { ); } +test "zig fmt: comments in ternary ifs" { + try testCanonical( + \\const x = if (true) { + \\ 1; + \\} else if (false) + \\ // Comment + \\ 0; + \\const y = if (true) + \\ // Comment + \\ 1 + \\else + \\ 0; + \\ + \\pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; + \\ + ); +} + +test "zig fmt: test comments in field access chain" { + try testCanonical( + \\pub const str = struct { + \\ pub const Thing = more.more // + \\ .more() // + \\ .more().more() // + \\ .more() // + \\ // .more() // + \\ .more() // + \\ .more(); + \\ data: Data, + \\}; + \\ + \\pub const str = struct { + \\ pub const Thing = more.more // + \\ .more() // + \\ // .more() // + \\ // .more() // + \\ // .more() // + \\ .more() // + \\ .more(); + \\ data: Data, + \\}; + \\ + \\pub const str = struct { + \\ pub const Thing = more // + \\ .more // + \\ .more() // + \\ .more(); + \\ data: Data, + \\}; + \\ + ); +} + +test "zig fmt: Indent comma correctly after multiline string literals in arg list (trailing comma)" { + try testCanonical( + \\fn foo() void { + \\ z.display_message_dialog( + \\ *const [323:0]u8, + \\ \\Message Text + \\ \\------------ + \\ \\xxxxxxxxxxxx + \\ \\xxxxxxxxxxxx + \\ , + \\ g.GtkMessageType.GTK_MESSAGE_WARNING, + \\ null, + \\ ); + \\ + \\ z.display_message_dialog(*const [323:0]u8, + \\ \\Message Text + \\ \\------------ + \\ \\xxxxxxxxxxxx + \\ \\xxxxxxxxxxxx + \\ , g.GtkMessageType.GTK_MESSAGE_WARNING, null); + \\} + \\ + ); +} + +test "zig fmt: Control flow statement as body of blockless if" { + try testCanonical( + \\pub fn main() void { + \\ const zoom_node = if (focused_node == layout_first) + \\ if (it.next()) { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ } else null + \\ else + \\ focused_node; + \\ + \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ } else null else + \\ focused_node; + \\ + \\ const zoom_node = if (focused_node == layout_first) + \\ if (it.next()) { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ } else null; + \\ + \\ const zoom_node = if (focused_node == layout_first) while (it.next()) |node| { + \\ if (!node.view.pending.float and !node.view.pending.fullscreen) break node; + \\ }; + \\ + \\ const zoom_node = if (focused_node == layout_first) for (nodes) |node| { + \\ break node; + \\ }; + \\ + \\ const zoom_node = if (focused_node == layout_first) switch (nodes) { + \\ 0 => 0, + \\ } else + \\ focused_node; + \\} + \\ + ); +} + +test "zig fmt: " { + try testCanonical( + \\pub fn sendViewTags(self: Self) void { + \\ var it = ViewStack(View).iterator(self.output.views.first, std.math.maxInt(u32)); + \\ while (it.next()) |node| + \\ view_tags.append(node.view.current_tags) catch { + \\ c.wl_resource_post_no_memory(self.wl_resource); + \\ log.crit(.river_status, "out of memory", .{}); + \\ return; + \\ }; + \\} + \\ + ); +} + +test "zig fmt: allow trailing line comments to do manual array formatting" { + try testCanonical( + \\fn foo() void { + \\ self.code.appendSliceAssumeCapacity(&[_]u8{ + \\ 0x55, // push rbp + \\ 0x48, 0x89, 0xe5, // mov rbp, rsp + \\ 0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc) + \\ }); + \\ + \\ di_buf.appendAssumeCapacity(&[_]u8{ + \\ 1, DW.TAG_compile_unit, DW.CHILDREN_no, // header + \\ DW.AT_stmt_list, DW_FORM_data4, // form value pairs + \\ DW.AT_low_pc, DW_FORM_addr, + \\ DW.AT_high_pc, DW_FORM_addr, + \\ DW.AT_name, DW_FORM_strp, + \\ DW.AT_comp_dir, DW_FORM_strp, + \\ DW.AT_producer, DW_FORM_strp, + \\ DW.AT_language, DW_FORM_data2, + \\ 0, 0, // sentinel + \\ }); + \\ + \\ self.code.appendSliceAssumeCapacity(&[_]u8{ + \\ 0x55, // push rbp + \\ 0x48, 0x89, 0xe5, // mov rbp, rsp + \\ // How do we handle this? + \\ //0x48, 0x81, 0xec, // sub rsp, imm32 (with reloc) + \\ // Here's a blank line, should that be allowed? + \\ + \\ 0x48, 0x89, 0xe5, + \\ 0x33, 0x45, + \\ // Now the comment breaks a single line -- how do we handle this? + \\ 0x88, + \\ }); + \\} + \\ + ); +} + +test "zig fmt: multiline string literals should play nice with array initializers" { + try testCanonical( + \\fn main() void { + \\ var a = .{.{.{.{.{.{.{.{ + \\ 0, + \\ }}}}}}}}; + \\ myFunc(.{ + \\ "aaaaaaa", "bbbbbb", "ccccc", + \\ "dddd", ("eee"), ("fff"), + \\ ("gggg"), + \\ // Line comment + \\ \\Multiline String Literals can be quite long + \\ , + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ , + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ \\Multiline String Literals can be quite long + \\ , + \\ ( + \\ \\Multiline String Literals can be quite long + \\ ), + \\ .{ + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ }, + \\ .{( + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ )}, + \\ .{ + \\ "xxxxxxx", "xxx", + \\ ( + \\ \\ xxx + \\ ), + \\ "xxx", "xxx", + \\ }, + \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, .{ "xxxxxxx", "xxx", "xxx", "xxx" }, + \\ "aaaaaaa", "bbbbbb", "ccccc", // - + \\ "dddd", ("eee"), ("fff"), + \\ .{ + \\ "xxx", "xxx", + \\ ( + \\ \\ xxx + \\ ), + \\ "xxxxxxxxxxxxxx", "xxx", + \\ }, + \\ .{ + \\ ( + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ ), + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ }, + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ \\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + \\ }); + \\} + \\ + ); +} + +test "zig fmt: use of comments and Multiline string literals may force the parameters over multiple lines" { + try testCanonical( + \\pub fn makeMemUndefined(qzz: []u8) i1 { + \\ cases.add( // fixed bug #2032 + \\ "compile diagnostic string for top level decl type", + \\ \\export fn entry() void { + \\ \\ var foo: u32 = @This(){}; + \\ \\} + \\ , &[_][]const u8{ + \\ "tmp.zig:2:27: error: type 'u32' does not support array initialization", + \\ }); + \\ @compileError( + \\ \\ unknown-length pointers and C pointers cannot be hashed deeply. + \\ \\ Consider providing your own hash function. + \\ \\ unknown-length pointers and C pointers cannot be hashed deeply. + \\ \\ Consider providing your own hash function. + \\ ); + \\ return @intCast(i1, doMemCheckClientRequestExpr(0, // default return + \\ .MakeMemUndefined, @ptrToInt(qzz.ptr), qzz.len, 0, 0, 0)); + \\} + \\ + \\// This looks like garbage don't do this + \\const rparen = tree.prevToken( + \\// the first token for the annotation expressions is the left + \\// parenthesis, hence the need for two prevToken + \\ if (fn_proto.getAlignExpr()) |align_expr| + \\ tree.prevToken(tree.prevToken(align_expr.firstToken())) + \\else if (fn_proto.getSectionExpr()) |section_expr| + \\ tree.prevToken(tree.prevToken(section_expr.firstToken())) + \\else if (fn_proto.getCallconvExpr()) |callconv_expr| + \\ tree.prevToken(tree.prevToken(callconv_expr.firstToken())) + \\else switch (fn_proto.return_type) { + \\ .Explicit => |node| node.firstToken(), + \\ .InferErrorSet => |node| tree.prevToken(node.firstToken()), + \\ .Invalid => unreachable, + \\}); + \\ + ); +} + +test "zig fmt: single argument trailing commas in @builtins()" { + try testCanonical( + \\pub fn foo(qzz: []u8) i1 { + \\ @panic( + \\ foo, + \\ ); + \\ panic( + \\ foo, + \\ ); + \\ @panic( + \\ foo, + \\ bar, + \\ ); + \\} + \\ + ); +} + +test "zig fmt: trailing comma should force multiline 1 column" { + try testTransform( + \\pub const UUID_NULL: uuid_t = [16]u8{0,0,0,0,}; + \\ + , + \\pub const UUID_NULL: uuid_t = [16]u8{ + \\ 0, + \\ 0, + \\ 0, + \\ 0, + \\}; + \\ + ); +} + +test "zig fmt: function params should align nicely" { + try testCanonical( + \\pub fn foo() void { + \\ cases.addRuntimeSafety("slicing operator with sentinel", + \\ \\const std = @import("std"); + \\ ++ check_panic_msg ++ + \\ \\pub fn main() void { + \\ \\ var buf = [4]u8{'a','b','c',0}; + \\ \\ const slice = buf[0..:0]; + \\ \\} + \\ ); + \\} + \\ + ); +} + const std = @import("std"); const mem = std.mem; const warn = std.debug.warn; diff --git a/lib/std/zig/render.zig b/lib/std/zig/render.zig index 237ca07d2b99..67afbb77d952 100644 --- a/lib/std/zig/render.zig +++ b/lib/std/zig/render.zig @@ -522,7 +522,11 @@ fn renderExpression( break :blk if (loc.line == 0) op_space else Space.Newline; }; - try renderToken(tree, ais, infix_op_node.op_token, after_op_space); + { + ais.pushIndent(); + defer ais.popIndent(); + try renderToken(tree, ais, infix_op_node.op_token, after_op_space); + } ais.pushIndentOneShot(); return renderExpression(allocator, ais, tree, infix_op_node.rhs, space); }, @@ -710,141 +714,194 @@ fn renderExpression( .node => |node| tree.nextToken(node.lastToken()), }; - if (exprs.len == 0) { - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - - { - ais.pushIndent(); - defer ais.popIndent(); - try renderToken(tree, ais, lbrace, Space.None); - } + switch (lhs) { + .dot => |dot| try renderToken(tree, ais, dot, Space.None), + .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), + } + if (exprs.len == 0) { + try renderToken(tree, ais, lbrace, Space.None); return renderToken(tree, ais, rtoken, space); } - if (exprs.len == 1 and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) { + + if (exprs.len == 1 and exprs[0].tag != .MultilineStringLiteral and tree.token_ids[exprs[0].*.lastToken() + 1] == .RBrace) { const expr = exprs[0]; - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } try renderToken(tree, ais, lbrace, Space.None); try renderExpression(allocator, ais, tree, expr, Space.None); return renderToken(tree, ais, rtoken, space); } - switch (lhs) { - .dot => |dot| try renderToken(tree, ais, dot, Space.None), - .node => |node| try renderExpression(allocator, ais, tree, node, Space.None), - } - // scan to find row size - const maybe_row_size: ?usize = blk: { - var count: usize = 1; - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const expr_last_token = expr.lastToken() + 1; - const loc = tree.tokenLocation(tree.token_locs[expr_last_token].end, exprs[i + 1].firstToken()); - if (loc.line != 0) break :blk count; - count += 1; - } else { - const expr_last_token = expr.lastToken(); - const loc = tree.tokenLocation(tree.token_locs[expr_last_token].end, rtoken); - if (loc.line == 0) { - // all on one line - const src_has_trailing_comma = trailblk: { - const maybe_comma = tree.prevToken(rtoken); - break :trailblk tree.token_ids[maybe_comma] == .Comma; - }; - if (src_has_trailing_comma) { - break :blk 1; // force row size 1 - } else { - break :blk null; // no newlines - } - } - break :blk count; - } - } - unreachable; - }; - - if (maybe_row_size) |row_size| { - // A place to store the width of each expression and its column's maximum - var widths = try allocator.alloc(usize, exprs.len + row_size); - defer allocator.free(widths); - mem.set(usize, widths, 0); - - var expr_widths = widths[0 .. widths.len - row_size]; - var column_widths = widths[widths.len - row_size ..]; - - // Null ais for counting the printed length of each expression - var counting_stream = std.io.countingOutStream(std.io.null_out_stream); - var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); - - for (exprs) |expr, i| { - counting_stream.bytes_written = 0; - try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); - const width = @intCast(usize, counting_stream.bytes_written); - const col = i % row_size; - column_widths[col] = std.math.max(column_widths[col], width); - expr_widths[i] = width; - } - + if (rowSize(tree, exprs, rtoken) != null) { { ais.pushIndentNextLine(); defer ais.popIndent(); try renderToken(tree, ais, lbrace, Space.Newline); - var col: usize = 1; - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const next_expr = exprs[i + 1]; - try renderExpression(allocator, ais, tree, expr, Space.None); - - const comma = tree.nextToken(expr.*.lastToken()); - - if (col != row_size) { - try renderToken(tree, ais, comma, Space.Space); // , - - const padding = column_widths[i % row_size] - expr_widths[i]; - try ais.writer().writeByteNTimes(' ', padding); - - col += 1; - continue; + var expr_index: usize = 0; + while (rowSize(tree, exprs[expr_index..], rtoken)) |row_size| { + const row_exprs = exprs[expr_index..]; + // A place to store the width of each expression and its column's maximum + var widths = try allocator.alloc(usize, row_exprs.len + row_size); + defer allocator.free(widths); + mem.set(usize, widths, 0); + + var expr_newlines = try allocator.alloc(bool, row_exprs.len); + defer allocator.free(expr_newlines); + mem.set(bool, expr_newlines, false); + + var expr_widths = widths[0 .. widths.len - row_size]; + var column_widths = widths[widths.len - row_size ..]; + + // Find next row with trailing comment (if any) to end the current section + var section_end = sec_end: { + var this_line_first_expr: usize = 0; + var this_line_size = rowSize(tree, row_exprs, rtoken); + for (row_exprs) |expr, i| { + // Ignore comment on first line of this section + if (i == 0 or tree.tokensOnSameLine(row_exprs[0].firstToken(), expr.lastToken())) continue; + // Track start of line containing comment + if (!tree.tokensOnSameLine(row_exprs[this_line_first_expr].firstToken(), expr.lastToken())) { + this_line_first_expr = i; + this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rtoken); + } + + const maybe_comma = expr.lastToken() + 1; + const maybe_comment = expr.lastToken() + 2; + if (maybe_comment < tree.token_ids.len) { + if (tree.token_ids[maybe_comma] == .Comma and + tree.token_ids[maybe_comment] == .LineComment and + tree.tokensOnSameLine(expr.lastToken(), maybe_comment)) + { + var comment_token_loc = tree.token_locs[maybe_comment]; + const comment_is_empty = mem.trimRight(u8, tree.tokenSliceLoc(comment_token_loc), " ").len == 2; + if (!comment_is_empty) { + // Found row ending in comment + break :sec_end i - this_line_size.? + 1; + } + } + } } - col = 1; + break :sec_end row_exprs.len; + }; + expr_index += section_end; + + const section_exprs = row_exprs[0..section_end]; + + // Null stream for counting the printed length of each expression + var line_find_stream = std.io.findByteOutStream('\n', std.io.null_out_stream); + var counting_stream = std.io.countingOutStream(line_find_stream.writer()); + var auto_indenting_stream = std.io.autoIndentingStream(indent_delta, counting_stream.writer()); + + // Calculate size of columns in current section + var column_counter: usize = 0; + var single_line = true; + for (section_exprs) |expr, i| { + if (i + 1 < section_exprs.len) { + counting_stream.bytes_written = 0; + line_find_stream.byte_found = false; + try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); + const width = @intCast(usize, counting_stream.bytes_written); + expr_widths[i] = width; + expr_newlines[i] = line_find_stream.byte_found; + + if (!line_find_stream.byte_found) { + const column = column_counter % row_size; + column_widths[column] = std.math.max(column_widths[column], width); + + const expr_last_token = expr.*.lastToken() + 1; + const next_expr = section_exprs[i + 1]; + const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, next_expr.*.firstToken()); + if (loc.line == 0) { + column_counter += 1; + } else { + single_line = false; + column_counter = 0; + } + } else { + single_line = false; + column_counter = 0; + } + } else { + counting_stream.bytes_written = 0; + try renderExpression(allocator, &auto_indenting_stream, tree, expr, Space.None); + const width = @intCast(usize, counting_stream.bytes_written); + expr_widths[i] = width; + expr_newlines[i] = line_find_stream.byte_found; + + if (!line_find_stream.byte_found) { + const column = column_counter % row_size; + column_widths[column] = std.math.max(column_widths[column], width); + } + break; + } + } - if (tree.token_ids[tree.nextToken(comma)] != .MultilineStringLiteralLine) { + // Render exprs in current section + column_counter = 0; + var last_col_index: usize = row_size - 1; + for (section_exprs) |expr, i| { + if (i + 1 < section_exprs.len) { + const next_expr = section_exprs[i + 1]; + try renderExpression(allocator, ais, tree, expr, Space.None); + + const comma = tree.nextToken(expr.*.lastToken()); + + if (column_counter != last_col_index) { + if (!expr_newlines[i] and !expr_newlines[i + 1]) { + // Neither the current or next expression is multiline + try renderToken(tree, ais, comma, Space.Space); // , + assert(column_widths[column_counter % row_size] >= expr_widths[i]); + const padding = column_widths[column_counter % row_size] - expr_widths[i]; + try ais.writer().writeByteNTimes(' ', padding); + + column_counter += 1; + continue; + } + } + if (single_line and row_size != 1) { + try renderToken(tree, ais, comma, Space.Space); // , + continue; + } + + column_counter = 0; try renderToken(tree, ais, comma, Space.Newline); // , + try renderExtraNewline(tree, ais, next_expr); } else { - try renderToken(tree, ais, comma, Space.None); // , + const maybe_comma = tree.nextToken(expr.*.lastToken()); + if (tree.token_ids[maybe_comma] == .Comma) { + try renderExpression(allocator, ais, tree, expr, Space.None); // , + try renderToken(tree, ais, maybe_comma, Space.Newline); // , + } else { + try renderExpression(allocator, ais, tree, expr, Space.Comma); // , + } } + } - try renderExtraNewline(tree, ais, next_expr); - } else { - try renderExpression(allocator, ais, tree, expr, Space.Comma); // , + if (expr_index == exprs.len) { + break; } } } - return renderToken(tree, ais, rtoken, space); - } else { - try renderToken(tree, ais, lbrace, Space.Space); - for (exprs) |expr, i| { - if (i + 1 < exprs.len) { - const next_expr = exprs[i + 1]; - try renderExpression(allocator, ais, tree, expr, Space.None); - const comma = tree.nextToken(expr.*.lastToken()); - try renderToken(tree, ais, comma, Space.Space); // , - } else { - try renderExpression(allocator, ais, tree, expr, Space.Space); - } - } return renderToken(tree, ais, rtoken, space); } + + // Single line + try renderToken(tree, ais, lbrace, Space.Space); + for (exprs) |expr, i| { + if (i + 1 < exprs.len) { + const next_expr = exprs[i + 1]; + try renderExpression(allocator, ais, tree, expr, Space.None); + const comma = tree.nextToken(expr.*.lastToken()); + try renderToken(tree, ais, comma, Space.Space); // , + } else { + try renderExpression(allocator, ais, tree, expr, Space.Space); + } + } + + return renderToken(tree, ais, rtoken, space); }, .StructInitializer, .StructInitializerDot => { @@ -1004,21 +1061,29 @@ fn renderExpression( }; if (src_has_trailing_comma) { - try renderToken(tree, ais, lparen, Space.Newline); - - const params = call.params(); - for (params) |param_node, i| { + { ais.pushIndent(); defer ais.popIndent(); - if (i + 1 < params.len) { - const next_node = params[i + 1]; - try renderExpression(allocator, ais, tree, param_node, Space.None); - const comma = tree.nextToken(param_node.lastToken()); - try renderToken(tree, ais, comma, Space.Newline); // , - try renderExtraNewline(tree, ais, next_node); - } else { - try renderExpression(allocator, ais, tree, param_node, Space.Comma); + try renderToken(tree, ais, lparen, Space.Newline); // ( + const params = call.params(); + for (params) |param_node, i| { + if (i + 1 < params.len) { + const next_node = params[i + 1]; + try renderExpression(allocator, ais, tree, param_node, Space.None); + + // Unindent the comma for multiline string literals + const maybe_multiline_string = param_node.firstToken(); + const is_multiline_string = tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine; + if (is_multiline_string) ais.popIndent(); + defer if (is_multiline_string) ais.pushIndent(); + + const comma = tree.nextToken(param_node.lastToken()); + try renderToken(tree, ais, comma, Space.Newline); // , + try renderExtraNewline(tree, ais, next_node); + } else { + try renderExpression(allocator, ais, tree, param_node, Space.Comma); + } } } return renderToken(tree, ais, call.rtoken, space); @@ -1028,17 +1093,20 @@ fn renderExpression( const params = call.params(); for (params) |param_node, i| { - if (param_node.*.tag == .MultilineStringLiteral) ais.pushIndentOneShot(); + const maybe_comment = param_node.firstToken() - 1; + const maybe_multiline_string = param_node.firstToken(); + if (tree.token_ids[maybe_multiline_string] == .MultilineStringLiteralLine or tree.token_ids[maybe_comment] == .LineComment) { + ais.pushIndentOneShot(); + } try renderExpression(allocator, ais, tree, param_node, Space.None); if (i + 1 < params.len) { - const next_param = params[i + 1]; const comma = tree.nextToken(param_node.lastToken()); try renderToken(tree, ais, comma, Space.Space); } } - return renderToken(tree, ais, call.rtoken, space); + return renderToken(tree, ais, call.rtoken, space); // ) }, .ArrayAccess => { @@ -1429,7 +1497,7 @@ fn renderExpression( try renderToken(tree, ais, builtin_call.builtin_token, Space.None); // @name const src_params_trailing_comma = blk: { - if (builtin_call.params_len < 2) break :blk false; + if (builtin_call.params_len == 0) break :blk false; const last_node = builtin_call.params()[builtin_call.params_len - 1]; const maybe_comma = tree.nextToken(last_node.lastToken()); break :blk tree.token_ids[maybe_comma] == .Comma; @@ -1443,6 +1511,10 @@ fn renderExpression( // render all on one line, no trailing comma const params = builtin_call.params(); for (params) |param_node, i| { + const maybe_comment = param_node.firstToken() - 1; + if (param_node.*.tag == .MultilineStringLiteral or tree.token_ids[maybe_comment] == .LineComment) { + ais.pushIndentOneShot(); + } try renderExpression(allocator, ais, tree, param_node, Space.None); if (i + 1 < params.len) { @@ -1494,19 +1566,20 @@ fn renderExpression( assert(tree.token_ids[lparen] == .LParen); const rparen = tree.prevToken( - // the first token for the annotation expressions is the left - // parenthesis, hence the need for two prevToken - if (fn_proto.getAlignExpr()) |align_expr| - tree.prevToken(tree.prevToken(align_expr.firstToken())) - else if (fn_proto.getSectionExpr()) |section_expr| - tree.prevToken(tree.prevToken(section_expr.firstToken())) - else if (fn_proto.getCallconvExpr()) |callconv_expr| - tree.prevToken(tree.prevToken(callconv_expr.firstToken())) - else switch (fn_proto.return_type) { - .Explicit => |node| node.firstToken(), - .InferErrorSet => |node| tree.prevToken(node.firstToken()), - .Invalid => unreachable, - }); + // the first token for the annotation expressions is the left + // parenthesis, hence the need for two prevToken + if (fn_proto.getAlignExpr()) |align_expr| + tree.prevToken(tree.prevToken(align_expr.firstToken())) + else if (fn_proto.getSectionExpr()) |section_expr| + tree.prevToken(tree.prevToken(section_expr.firstToken())) + else if (fn_proto.getCallconvExpr()) |callconv_expr| + tree.prevToken(tree.prevToken(callconv_expr.firstToken())) + else switch (fn_proto.return_type) { + .Explicit => |node| node.firstToken(), + .InferErrorSet => |node| tree.prevToken(node.firstToken()), + .Invalid => unreachable, + }, + ); assert(tree.token_ids[rparen] == .RParen); const src_params_trailing_comma = blk: { @@ -1758,7 +1831,7 @@ fn renderExpression( } if (while_node.payload) |payload| { - const payload_space = Space.Space; //if (while_node.continue_expr != null) Space.Space else block_start_space; + const payload_space = if (while_node.continue_expr != null) Space.Space else block_start_space; try renderExpression(allocator, ais, tree, payload, payload_space); } @@ -1873,7 +1946,12 @@ fn renderExpression( if (src_has_newline) { const after_rparen_space = if (if_node.payload == null) Space.Newline else Space.Space; - try renderToken(tree, ais, rparen, after_rparen_space); // ) + + { + ais.pushIndent(); + defer ais.popIndent(); + try renderToken(tree, ais, rparen, after_rparen_space); // ) + } if (if_node.payload) |payload| { try renderExpression(allocator, ais, tree, payload, Space.Newline); @@ -2558,3 +2636,27 @@ fn copyFixingWhitespace(ais: anytype, slice: []const u8) @TypeOf(ais.*).Error!vo else => try ais.writer().writeByte(byte), }; } + +fn rowSize(tree: *ast.Tree, exprs: []*ast.Node, rtoken: ast.TokenIndex) ?usize { + const first_token = exprs[0].firstToken(); + const first_loc = tree.tokenLocation(tree.token_locs[first_token].start, rtoken); + if (first_loc.line == 0) { + const maybe_comma = tree.prevToken(rtoken); + if (tree.token_ids[maybe_comma] == .Comma) + return 1; + return null; // no newlines + } + + var count: usize = 1; + for (exprs) |expr, i| { + if (i + 1 < exprs.len) { + const expr_last_token = expr.lastToken() + 1; + const loc = tree.tokenLocation(tree.token_locs[expr_last_token].start, exprs[i + 1].firstToken()); + if (loc.line != 0) return count; + count += 1; + } else { + return count; + } + } + unreachable; +}