zig fmt: rework single statement if/while/for indentation

This approach properly handles nesting unlike the approach in the
previous commit.
This commit is contained in:
Isaac Freund 2021-02-24 16:44:55 +01:00
parent 371b21bdfb
commit 52c45bf44d
No known key found for this signature in database
GPG Key ID: 86DED400DDFD7A11
2 changed files with 102 additions and 65 deletions

View File

@ -1496,12 +1496,14 @@ test "zig fmt: if nested" {
\\ GE_EQUAL
\\ else
\\ GE_GREATER
\\ // comment
\\ else if (aInt > bInt)
\\ GE_LESS
\\ else if (aInt == bInt)
\\ GE_EQUAL
\\ else
\\ GE_GREATER;
\\ // comment
\\}
\\
);
@ -4189,6 +4191,21 @@ test "zig fmt: line comment after multiline single expr if statement with multil
\\
\\ // bar
\\ baz();
\\
\\ if (foo)
\\ x =
\\ \\hello
\\ \\hello
\\ \\
\\ else
\\ y =
\\ \\hello
\\ \\hello
\\ \\
\\ ;
\\
\\ // bar
\\ baz();
\\}
\\
);

View File

@ -44,7 +44,6 @@ pub fn renderTree(buffer: *std.ArrayList(u8), tree: ast.Tree) Error!void {
/// Render all members in the given slice, keeping empty lines where appropriate
fn renderMembers(gpa: *Allocator, ais: *Ais, tree: ast.Tree, members: []const ast.Node.Index) Error!void {
if (members.len == 0) return;
//try renderExtraNewline(ais, tree, members[0]);
try renderMember(gpa, ais, tree, members[0], .newline);
for (members[1..]) |member| {
try renderExtraNewline(ais, tree, member);
@ -202,10 +201,8 @@ fn renderExpression(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.I
while (locked_indents > 0) : (locked_indents -= 1) ais.popIndent();
switch (space) {
.none, .space, .newline => {},
.newline_pop => ais.popIndent(),
.none, .space, .newline, .skip => {},
.semicolon => if (token_tags[i] == .semicolon) try renderToken(ais, tree, i, .newline),
.semicolon_pop => if (token_tags[i] == .semicolon) try renderToken(ais, tree, i, .newline_pop),
.comma => if (token_tags[i] == .comma) try renderToken(ais, tree, i, .newline),
.comma_space => if (token_tags[i] == .comma) try renderToken(ais, tree, i, .space),
}
@ -1147,15 +1144,11 @@ fn renderWhile(gpa: *Allocator, ais: *Ais, tree: ast.Tree, while_node: ast.full.
} else {
try renderToken(ais, tree, while_node.else_token, .newline); // else
}
ais.pushIndent();
try renderExpression(gpa, ais, tree, while_node.ast.else_expr, space);
ais.popIndent();
try renderExpressionIndented(gpa, ais, tree, while_node.ast.else_expr, space);
return;
}
} else {
ais.pushIndent();
assert(space == .semicolon);
try renderExpression(gpa, ais, tree, while_node.ast.then_expr, .semicolon_pop);
try renderExpressionIndented(gpa, ais, tree, while_node.ast.then_expr, space);
return;
}
}
@ -2168,6 +2161,64 @@ fn renderCall(
return renderToken(ais, tree, after_last_param_tok, space); // )
}
/// Renders the given expression indented, popping the indent before rendering
/// any following line comments
fn renderExpressionIndented(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void {
const token_starts = tree.tokens.items(.start);
const token_tags = tree.tokens.items(.tag);
ais.pushIndent();
var last_token = tree.lastToken(node);
const punctuation = switch (space) {
.none, .space, .newline, .skip => false,
.comma => true,
.comma_space => token_tags[last_token + 1] == .comma,
.semicolon => token_tags[last_token + 1] == .semicolon,
};
try renderExpression(gpa, ais, tree, node, if (punctuation) .none else .skip);
switch (space) {
.none, .space, .newline, .skip => {},
.comma => {
if (token_tags[last_token + 1] == .comma) {
try renderToken(ais, tree, last_token + 1, .skip);
last_token += 1;
} else {
try ais.writer().writeByte(',');
}
},
.comma_space => if (token_tags[last_token + 1] == .comma) {
try renderToken(ais, tree, last_token + 1, .skip);
last_token += 1;
},
.semicolon => if (token_tags[last_token + 1] == .semicolon) {
try renderToken(ais, tree, last_token + 1, .skip);
last_token += 1;
},
}
ais.popIndent();
if (space == .skip) return;
const comment_start = token_starts[last_token] + tokenSliceForRender(tree, last_token).len;
const comment = try renderComments(ais, tree, comment_start, token_starts[last_token + 1]);
if (!comment) switch (space) {
.none => {},
.space,
.comma_space,
=> try ais.writer().writeByte(' '),
.newline,
.comma,
.semicolon,
=> try ais.insertNewline(),
.skip => unreachable,
};
}
/// Render an expression, and the comma that follows it, if it is present in the source.
fn renderExpressionComma(gpa: *Allocator, ais: *Ais, tree: ast.Tree, node: ast.Node.Index, space: Space) Error!void {
const token_tags = tree.tokens.items(.tag);
@ -2198,9 +2249,6 @@ const Space = enum {
space,
/// Output the token lexeme followed by a newline.
newline,
/// Same as newline, but pop an indent level before rendering the
/// following comments if any.
newline_pop,
/// If the next token is a comma, render it as well. If not, insert one.
/// In either case, a newline will be inserted afterwards.
comma,
@ -2210,9 +2258,9 @@ const Space = enum {
/// Additionally consume the next token if it is a semicolon.
/// In either case, a newline will be inserted afterwards.
semicolon,
/// Same as semicolon, but pop an indent level before rendering the
/// following comments if any.
semicolon_pop,
/// Skip rendering whitespace and comments. If this is used, the caller
/// *must* handle handle whitespace and comments manually.
skip,
};
fn renderToken(ais: *Ais, tree: ast.Tree, token_index: ast.TokenIndex, space: Space) Error!void {
@ -2224,65 +2272,37 @@ fn renderToken(ais: *Ais, tree: ast.Tree, token_index: ast.TokenIndex, space: Sp
try ais.writer().writeAll(lexeme);
const token_end = token_start + lexeme.len;
const next_token_start = token_starts[token_index + 1];
if (space == .skip) return;
if (space == .comma and token_tags[token_index + 1] != .comma) {
try ais.writer().writeByte(',');
}
const comment = try renderComments(ais, tree, token_start + lexeme.len, token_starts[token_index + 1]);
switch (space) {
.none => _ = try renderComments(ais, tree, token_end, next_token_start),
.none => {},
.space => if (!comment) try ais.writer().writeByte(' '),
.newline => if (!comment) try ais.insertNewline(),
.space => if (!try renderComments(ais, tree, token_end, next_token_start)) {
try ais.writer().writeByte(' ');
},
.newline => if (!try renderComments(ais, tree, token_end, next_token_start)) {
.comma => if (token_tags[token_index + 1] == .comma) {
try renderToken(ais, tree, token_index + 1, .newline);
} else if (!comment) {
try ais.insertNewline();
},
.newline_pop => {
ais.popIndent();
if (!try renderComments(ais, tree, token_end, next_token_start)) {
try ais.insertNewline();
}
.comma_space => if (token_tags[token_index + 1] == .comma) {
try renderToken(ais, tree, token_index + 1, .space);
} else if (!comment) {
try ais.writer().writeByte(' ');
},
.comma => if (token_tags[token_index + 1] == .comma) {
_ = try renderComments(ais, tree, token_end, next_token_start);
.semicolon => if (token_tags[token_index + 1] == .semicolon) {
try renderToken(ais, tree, token_index + 1, .newline);
} else {
try ais.writer().writeByte(',');
if (!try renderComments(ais, tree, token_end, next_token_start)) {
try ais.insertNewline();
}
} else if (!comment) {
try ais.insertNewline();
},
.comma_space => {
const comment = try renderComments(ais, tree, token_end, next_token_start);
if (token_tags[token_index + 1] == .comma) {
try renderToken(ais, tree, token_index + 1, .space);
} else if (!comment) {
try ais.writer().writeByte(' ');
}
},
.semicolon => {
const comment = try renderComments(ais, tree, token_end, next_token_start);
if (token_tags[token_index + 1] == .semicolon) {
try renderToken(ais, tree, token_index + 1, .newline);
} else if (!comment) {
try ais.insertNewline();
}
},
.semicolon_pop => {
if (token_tags[token_index + 1] == .semicolon) {
_ = try renderComments(ais, tree, token_end, next_token_start);
try renderToken(ais, tree, token_index + 1, .newline_pop);
} else {
ais.popIndent();
if (!try renderComments(ais, tree, token_end, next_token_start)) {
try ais.insertNewline();
}
}
},
.skip => unreachable,
}
}