zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit e03851e522738b6ed2259e742e5f2e2f4676ef75 (tree)
parent b0db0ef4e197eb44faa0e343874dfc8f810324ca
Author: Kendall Condon <goon.pri.low@gmail.com>
Date:   Mon, 28 Jul 2025 16:41:48 -0400

zig fmt: fix array size indenting when expr becomes multiline

Adds a new function becomesMultilineExpr to determine this and extracts
the logic from several functions.

Diffstat:
Mlib/std/zig/Ast/Render.zig | 513+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mlib/std/zig/parser_test.zig | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 692 insertions(+), 63 deletions(-)

diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig @@ -645,7 +645,8 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const lhs, const rhs = tree.nodeData(node).node_and_node; const lbracket = tree.firstToken(rhs) - 1; const rbracket = tree.lastToken(rhs) + 1; - const one_line = tree.tokensOnSameLine(lbracket, rbracket); + const one_line = tree.tokensOnSameLine(lbracket, rbracket) and + !becomesMultilineExpr(tree, rhs); const inner_space = if (one_line) Space.none else Space.newline; try renderExpression(r, lhs, .none); try ais.pushIndent(.normal); @@ -925,6 +926,382 @@ fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!v } } +/// Same as becomesMultilineExpr, but returns false when `node == .none` +fn optBecomesMultilineExpr(tree: Ast, node: Ast.Node.OptionalIndex) bool { + return if (node.unwrap()) |payload| becomesMultilineExpr(tree, payload) else false; +} + +/// May return false if `node` is already multiline +fn becomesMultilineExpr(tree: Ast, node: Ast.Node.Index) bool { + // Conditions related to comments, doc comments, and multiline string literals are ignored + // since they always go to the end of the line, which already make them a multi-line + // expression (since they contain a newline). + switch (tree.nodeTag(node)) { + .identifier, + .number_literal, + .char_literal, + .unreachable_literal, + .anyframe_literal, + .string_literal, + .multiline_string_literal, + .error_value, + .enum_literal, + => return false, + .container_decl_trailing, + .container_decl_arg_trailing, + .container_decl_two_trailing, + .tagged_union_trailing, + .tagged_union_enum_tag_trailing, + .tagged_union_two_trailing, + .switch_comma, + .builtin_call_two_comma, + .builtin_call_comma, + .call_one_comma, + .call_comma, + .struct_init_one_comma, + .struct_init_dot_two_comma, + .struct_init_dot_comma, + .struct_init_comma, + .array_init_one_comma, + .array_init_dot_two_comma, + .array_init_dot_comma, + .array_init_comma, + // The following always have a non-zero amount of members + // which is also the condition for them to be multi-line. + .block, + .block_semicolon, + => return true, + .block_two, + .block_two_semicolon, + => return tree.nodeData(node).opt_node_and_opt_node[0] != .none, + .container_decl, + .container_decl_arg, + .container_decl_two, + .tagged_union, + .tagged_union_enum_tag, + .tagged_union_two, + => { + var buf: [2]Ast.Node.Index = undefined; + const full = tree.fullContainerDecl(&buf, node).?; + if (full.ast.arg.unwrap()) |arg| { + if (becomesMultilineExpr(tree, arg)) + return true; + } + // This does the same checks as `isOneLineContainerDecl`, however it avoids unnecessary + // checks related to comments and multiline strings, which would mean the container is + // already multiple lines. + for (full.ast.members) |member| { + if (tree.fullContainerField(member)) |field_full| { + for ([_]Ast.Node.OptionalIndex{ + field_full.ast.type_expr, + field_full.ast.align_expr, + field_full.ast.value_expr, + }) |opt_expr| { + if (opt_expr.unwrap()) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + } + } else return true; + } + return false; + }, + .error_set_decl => { + const lbrace, const rbrace = tree.nodeData(node).token_and_token; + return !isOneLineErrorSetDecl(tree, lbrace, rbrace); + }, + .@"switch" => { + const op, const extra_index = tree.nodeData(node).node_and_extra; + const case_range = tree.extraData(extra_index, Ast.Node.SubRange); + return @intFromEnum(case_range.end) - @intFromEnum(case_range.start) != 0 or + becomesMultilineExpr(tree, op); + }, + .for_simple, .@"for" => { + const full = tree.fullFor(node).?; + if (becomesMultilineExpr(tree, full.ast.then_expr) or + optBecomesMultilineExpr(tree, full.ast.else_expr)) + return true; + + for (full.ast.inputs) |expr| { + if (if (tree.nodeTag(expr) == .for_range) blk: { + const lhs, const rhs = tree.nodeData(expr).node_and_opt_node; + break :blk becomesMultilineExpr(tree, lhs) or optBecomesMultilineExpr(tree, rhs); + } else becomesMultilineExpr(tree, expr)) + return true; + } + const final_input_expr = full.ast.inputs[full.ast.inputs.len - 1]; + if (tree.tokenTag(tree.lastToken(final_input_expr) + 1) == .comma) + return true; + + const token_tags = tree.tokens.items(.tag); + const payload = full.payload_token; + const pipe = std.mem.indexOfScalarPos(Token.Tag, token_tags, payload, .pipe).?; + return token_tags[@intCast(pipe - 1)] == .comma; + }, + .while_simple, + .while_cont, + .@"while", + => { + const full = tree.fullWhile(node).?; + return becomesMultilineExpr(tree, full.ast.cond_expr) or + becomesMultilineExpr(tree, full.ast.then_expr) or + optBecomesMultilineExpr(tree, full.ast.cont_expr) or + optBecomesMultilineExpr(tree, full.ast.else_expr); + }, + .if_simple, + .@"if", + => { + const full = tree.fullIf(node).?; + return becomesMultilineExpr(tree, full.ast.cond_expr) or + becomesMultilineExpr(tree, full.ast.then_expr) or + optBecomesMultilineExpr(tree, full.ast.else_expr); + }, + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + => { + var buf: [1]Ast.Node.Index = undefined; + const fn_proto = tree.fullFnProto(&buf, node).?; + + for ([_]Ast.Node.OptionalIndex{ + fn_proto.ast.return_type, + fn_proto.ast.align_expr, + fn_proto.ast.addrspace_expr, + fn_proto.ast.section_expr, + fn_proto.ast.callconv_expr, + }) |opt_expr| { + if (opt_expr.unwrap()) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + } + for (fn_proto.ast.params) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + + const lparen = fn_proto.ast.fn_token + 1; + const return_type = fn_proto.ast.return_type.unwrap().?; + const maybe_bang = tree.firstToken(return_type) - 1; + const rparen = fnProtoRparen(tree, fn_proto, maybe_bang); + return !isOneLineFnProto(tree, fn_proto, lparen, rparen); + }, + .asm_simple, + => { + const lhs = tree.nodeData(node).node_and_token[0]; + return becomesMultilineExpr(tree, lhs); + }, + .@"asm", + => { + const lhs, const extra_index = tree.nodeData(node).node_and_extra; + const asm_extra = tree.extraData(extra_index, Ast.Node.Asm); + return @intFromEnum(asm_extra.items_end) - @intFromEnum(asm_extra.items_start) != 0 or + becomesMultilineExpr(tree, lhs) or optBecomesMultilineExpr(tree, asm_extra.clobbers); + }, + .array_type, .array_type_sentinel => { + const array_type = tree.fullArrayType(node).?; + const rbracket = tree.firstToken(array_type.ast.elem_type) - 1; + return !isOneLineArrayType(tree, array_type, rbracket) or + becomesMultilineExpr(tree, array_type.ast.elem_type); + }, + .array_access => { + const lhs, const rhs = tree.nodeData(node).node_and_node; + const lbracket = tree.firstToken(rhs) - 1; + const rbracket = tree.lastToken(rhs) + 1; + return !tree.tokensOnSameLine(lbracket, rbracket) or + becomesMultilineExpr(tree, lhs) or + becomesMultilineExpr(tree, rhs); + }, + .call_one, + .call, + .builtin_call_two, + .builtin_call, + .array_init_one, + .array_init_dot_two, + .array_init_dot, + .array_init, + .struct_init_one, + .struct_init_dot_two, + .struct_init_dot, + .struct_init, + => |tag| { + var buf: [2]Ast.Node.Index = undefined; + const opt_lhs: Ast.Node.OptionalIndex, const items = switch (tag) { + .call_one, .call => blk: { + const full = tree.fullCall(buf[0..1], node).?; + break :blk .{ full.ast.fn_expr.toOptional(), full.ast.params }; + }, + .builtin_call_two, .builtin_call => .{ .none, tree.builtinCallParams(&buf, node).? }, + .array_init_one, + .array_init_dot_two, + .array_init_dot, + .array_init, + => blk: { + const full = tree.fullArrayInit(&buf, node).?; + break :blk .{ full.ast.type_expr, full.ast.elements }; + }, + .struct_init_one, + .struct_init_dot_two, + .struct_init_dot, + .struct_init, + => blk: { + const full = tree.fullStructInit(&buf, node).?; + break :blk .{ full.ast.type_expr, full.ast.fields }; + }, + else => unreachable, + }; + if (opt_lhs.unwrap()) |lhs| { + if (becomesMultilineExpr(tree, lhs)) + return true; + } + for (items) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + return false; + }, + .assign_destructure => { + const full = tree.assignDestructure(node); + for (full.ast.variables) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + return becomesMultilineExpr(tree, full.ast.value_expr); + }, + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + => { + const full = tree.fullPtrType(node).?; + return becomesMultilineExpr(tree, full.ast.child_type) or + optBecomesMultilineExpr(tree, full.ast.sentinel) or + optBecomesMultilineExpr(tree, full.ast.align_node) or + optBecomesMultilineExpr(tree, full.ast.addrspace_node) or + optBecomesMultilineExpr(tree, full.ast.bit_range_start) or + optBecomesMultilineExpr(tree, full.ast.bit_range_end); + }, + .slice_open, + .slice, + .slice_sentinel, + => { + const full = tree.fullSlice(node).?; + return becomesMultilineExpr(tree, full.ast.sliced) or + becomesMultilineExpr(tree, full.ast.start) or + optBecomesMultilineExpr(tree, full.ast.end) or + optBecomesMultilineExpr(tree, full.ast.sentinel); + }, + .@"comptime", + .@"nosuspend", + .@"suspend", + .@"resume", + .bit_not, + .bool_not, + .negation, + .negation_wrap, + .optional_type, + .address_of, + .deref, + .@"try", + => return becomesMultilineExpr(tree, tree.nodeData(node).node), + .@"return" => return optBecomesMultilineExpr(tree, tree.nodeData(node).opt_node), + .field_access, + .unwrap_optional, + .grouped_expression, + => return becomesMultilineExpr(tree, tree.nodeData(node).node_and_token[0]), + .add, + .add_wrap, + .add_sat, + .array_cat, + .array_mult, + .bang_equal, + .bit_and, + .bit_or, + .shl, + .shl_sat, + .shr, + .bit_xor, + .bool_and, + .bool_or, + .div, + .equal_equal, + .greater_or_equal, + .greater_than, + .less_or_equal, + .less_than, + .merge_error_sets, + .mod, + .mul, + .mul_wrap, + .mul_sat, + .sub, + .sub_wrap, + .sub_sat, + .@"orelse", + .@"catch", + .error_union, + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_shl, + .assign_shl_sat, + .assign_shr, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_sub_sat, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_add_sat, + .assign_mul, + .assign_mul_wrap, + .assign_mul_sat, + => { + const lhs, const rhs = tree.nodeData(node).node_and_node; + return becomesMultilineExpr(tree, lhs) or becomesMultilineExpr(tree, rhs); + }, + .@"break", .@"continue" => { + const opt_expr = tree.nodeData(node).opt_token_and_opt_node[1]; + return optBecomesMultilineExpr(tree, opt_expr); + }, + .anyframe_type => return becomesMultilineExpr(tree, tree.nodeData(node).token_and_node[1]), + .@"errdefer", + .@"defer", + .for_range, + .switch_range, + .switch_case_one, + .switch_case_inline_one, + .switch_case, + .switch_case_inline, + .asm_output, + .asm_input, + .fn_decl, + .container_field, + .container_field_init, + .container_field_align, + .root, + .global_var_decl, + .local_var_decl, + .simple_var_decl, + .aligned_var_decl, + .test_decl, + => unreachable, + } +} + +fn isOneLineArrayType( + tree: Ast, + array_type: Ast.full.ArrayType, + rbracket: Ast.TokenIndex, +) bool { + return tree.tokensOnSameLine(array_type.ast.lbracket, rbracket) and + !becomesMultilineExpr(tree, array_type.ast.elem_count) and + !optBecomesMultilineExpr(tree, array_type.ast.sentinel); +} + fn renderArrayType( r: *Render, array_type: Ast.full.ArrayType, @@ -933,7 +1310,7 @@ fn renderArrayType( const tree = r.tree; const ais = r.ais; const rbracket = tree.firstToken(array_type.ast.elem_type) - 1; - const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket); + const one_line = isOneLineArrayType(tree, array_type, rbracket); const inner_space = if (one_line) Space.none else Space.newline; try ais.pushIndent(.normal); try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket @@ -1614,6 +1991,47 @@ fn renderBuiltinCall( return renderParamList(r, builtin_token + 1, params, space); } +fn fnProtoRparen(tree: Ast, fn_proto: Ast.full.FnProto, maybe_bang: Ast.TokenIndex) Ast.TokenIndex { + // These may appear in any order, so we have to check the token_starts array + // to find out which is first. + var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang; + var smallest_start = tree.tokenStart(maybe_bang); + if (fn_proto.ast.align_expr.unwrap()) |align_expr| { + const tok = tree.firstToken(align_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| { + const tok = tree.firstToken(addrspace_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + if (fn_proto.ast.section_expr.unwrap()) |section_expr| { + const tok = tree.firstToken(section_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| { + const tok = tree.firstToken(callconv_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + assert(tree.tokenTag(rparen) == .r_paren); + return rparen; +} + fn isOneLineFnProto( tree: Ast, fn_proto: Ast.full.FnProto, @@ -1652,46 +2070,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi const return_type = fn_proto.ast.return_type.unwrap().?; const maybe_bang = tree.firstToken(return_type) - 1; - const rparen = blk: { - // These may appear in any order, so we have to check the token_starts array - // to find out which is first. - var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang; - var smallest_start = tree.tokenStart(maybe_bang); - if (fn_proto.ast.align_expr.unwrap()) |align_expr| { - const tok = tree.firstToken(align_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| { - const tok = tree.firstToken(addrspace_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - if (fn_proto.ast.section_expr.unwrap()) |section_expr| { - const tok = tree.firstToken(section_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| { - const tok = tree.firstToken(callconv_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - break :blk rparen; - }; - assert(tree.tokenTag(rparen) == .r_paren); + const rparen = fnProtoRparen(tree, fn_proto, maybe_bang); // The params list is a sparse set that does *not* include anytype or ... parameters. @@ -2258,6 +2637,34 @@ fn isOneLineErrorSetDecl( !hasComment(tree, lbrace, rbrace); } +fn isOneLineContainerDecl( + tree: Ast, + container_decl: Ast.full.ContainerDecl, + lbrace: Ast.TokenIndex, + rbrace: Ast.TokenIndex, +) bool { + // We print all the members in one-line unless one of the following conditions are true: + + // 1. The container has comments or multiline strings. + if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) { + return false; + } + + // 2. The container has a container comment. + if (tree.tokenTag(lbrace + 1) == .container_doc_comment) return false; + + // 3. A member of the container has a doc comment. + if (hasDocComment(tree, lbrace + 1, rbrace)) + return false; + + // 4. The container has non-field members. + for (container_decl.ast.members) |member| { + if (tree.fullContainerField(member) == null) return false; + } + + return true; +} + fn renderContainerDecl( r: *Render, container_decl_node: Ast.Node.Index, @@ -2322,27 +2729,7 @@ fn renderContainerDecl( } const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma; - if (!src_has_trailing_comma) one_line: { - // We print all the members in-line unless one of the following conditions are true: - - // 1. The container has comments or multiline strings. - if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) { - break :one_line; - } - - // 2. The container has a container comment. - if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line; - - // 3. A member of the container has a doc comment. - for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| { - if (tag == .doc_comment) break :one_line; - } - - // 4. The container has non-field members. - for (container_decl.ast.members) |member| { - if (tree.fullContainerField(member) == null) break :one_line; - } - + if (!src_has_trailing_comma and isOneLineContainerDecl(tree, container_decl, lbrace, rbrace)) { // Print all the declarations on the same line. try renderToken(r, lbrace, .space); // lbrace for (container_decl.ast.members) |member| { diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig @@ -6094,6 +6094,248 @@ test "zig fmt: field accesses on number literals" { ); } +test "zig fmt: array indent when inner becomes multi-line" { + try testTransform( + \\const access_block = x[{{}}]; + \\ + \\const block = [{{}}]T; + \\const sentinel_block = [15:{{}}]T; + \\const container = [enum { A, }]T; + \\const container_arg = [union({{}}) {}]T; + \\const error_set = [error{ A, }]T; + \\const @"switch" = [switch (m) { 0 => {}, }]T; + \\const switch_op = [switch ({{}}) {}]T; + \\const for_capture = [for (a) |_,| {}]T; + \\const for_expr = [for ({{}}) |_| {}]T; + \\const for_range = [for ({{}}..15) |_| {}]T; + \\const for_input_comma = [for (0..15,) |_| {}]T; + \\const call_param = [a({{}})]T; + \\const call_fn = [({{}})()]T; + \\const builtin_call = [@log2({{}})]T; + \\const array_init = [.{{{}}}]T; + \\const struct_init = [.{.x = {{}}}]T; + \\const @"asm" = [asm ("" : [x] "" (-> T))]T; + \\const asm_template = [asm ({{}})]T; + \\const asm_clobbers = [asm ("" ::: {{}})]T; + \\const @"fn" = [fn (({{}})) void]T; + \\const array_type_len = [[{{}}]T]T; + \\const array_type_type = [[1]({{}})]T; + \\const array_access_array = [({{}})[1]]T; + \\const array_access_index = [x[{{}}]]T; + \\const binop = [1 + {{}}]T; + \\const unaryop = [!{{}}]T; + \\const destructure = [while (true) : (x, {{}} = z) {}]T; + \\ + , + \\const access_block = x[ + \\ { + \\ {} + \\ } + \\]; + \\ + \\const block = [ + \\ { + \\ {} + \\ } + \\]T; + \\const sentinel_block = [ + \\ 15 + \\ : + \\ { + \\ {} + \\ } + \\]T; + \\const container = [ + \\ enum { + \\ A, + \\ } + \\]T; + \\const container_arg = [ + \\ union({ + \\ {} + \\ }) {} + \\]T; + \\const error_set = [ + \\ error{ + \\ A, + \\ } + \\]T; + \\const @"switch" = [ + \\ switch (m) { + \\ 0 => {}, + \\ } + \\]T; + \\const switch_op = [ + \\ switch ({ + \\ {} + \\ }) {} + \\]T; + \\const for_capture = [ + \\ for (a) | + \\ _, + \\ | {} + \\]T; + \\const for_expr = [ + \\ for ({ + \\ {} + \\ }) |_| {} + \\]T; + \\const for_range = [ + \\ for ({ + \\ {} + \\ }..15) |_| {} + \\]T; + \\const for_input_comma = [ + \\ for ( + \\ 0..15, + \\ ) |_| {} + \\]T; + \\const call_param = [ + \\ a({ + \\ {} + \\ }) + \\]T; + \\const call_fn = [ + \\ ({ + \\ {} + \\ })() + \\]T; + \\const builtin_call = [ + \\ @log2({ + \\ {} + \\ }) + \\]T; + \\const array_init = [ + \\ .{{ + \\ {} + \\ }} + \\]T; + \\const struct_init = [ + \\ .{ .x = { + \\ {} + \\ } } + \\]T; + \\const @"asm" = [ + \\ asm ("" + \\ : [x] "" (-> T), + \\ ) + \\]T; + \\const asm_template = [ + \\ asm ({ + \\ {} + \\ }) + \\]T; + \\const asm_clobbers = [ + \\ asm ("" ::: { + \\ {} + \\ }) + \\]T; + \\const @"fn" = [ + \\ fn (({ + \\ {} + \\ })) void + \\]T; + \\const array_type_len = [ + \\ [ + \\ { + \\ {} + \\ } + \\ ]T + \\]T; + \\const array_type_type = [ + \\ [1]({ + \\ {} + \\ }) + \\]T; + \\const array_access_array = [ + \\ ({ + \\ {} + \\ })[1] + \\]T; + \\const array_access_index = [ + \\ x[ + \\ { + \\ {} + \\ } + \\ ] + \\]T; + \\const binop = [ + \\ 1 + { + \\ {} + \\ } + \\]T; + \\const unaryop = [ + \\ !{ + \\ {} + \\ } + \\]T; + \\const destructure = [ + \\ while (true) : (x, { + \\ {} + \\ } = z) {} + \\]T; + \\ + ); + + try testCanonical( + \\const oneline_access_block = x[{}]; + \\const oneline_block = [{}]T; + \\const oneline_sentinel_block = [15:{}]T; + \\const oneline_container = [enum { A }]T; + \\const oneline_error_set = [error{A}]T; + \\const oneline_switch = [switch (m) {}]T; + \\const oneline_for = [for (a, 0..15) |_, _| {}]T; + \\const oneline_call = [a(a)]T; + \\const oneline_builtin_call = [@log2(a)]T; + \\const oneline_array_init = [.{a}]T; + \\const oneline_struct_init = [.{ .x = a }]T; + \\const oneline_asm = [asm ("" ::: .{})]T; + \\const onlinee_fn = [fn (usize) void]T; + \\const oneline_array_type = [[{}]T]T; + \\const oneline_array_access = [x[{}]]T; + \\const online_binop = [1 + 1]T; + \\const online_unaryop = [!false]T; + \\const oneline_destructure = [while (true) : (x, y = z) {}]T; + \\ + ); + + try testTransform( + \\const a = [{ + \\}]T; + \\ + \\const b = x[{ + \\}]; + \\ + \\const c = [ + \\ { + \\ } + \\]T; + \\ + \\const d = x[ + \\ { + \\ } + \\]; + \\ + , + \\const a = [ + \\ {} + \\]T; + \\ + \\const b = x[ + \\ {} + \\]; + \\ + \\const c = [ + \\ {} + \\]T; + \\ + \\const d = x[ + \\ {} + \\]; + \\ + ); +} + test "zig fmt: whitespace with multiline strings" { try testCanonical( \\const a = .{