Skip to content

Commit ba88de7

Browse files
authored
Format function declarations and expressions. (#1303)
Format function declarations and expressions. This covers: - Top-level function declarations - Local function declaration statements - Anonymous function expressions It handles all three kinds of bodies: `;`, `{ ... }`, and `=> ... ;`. Most of the implementation work for functions is in: - Their parameter lists, which is most already handled by the existing support (and tests) for function type annotations. - Their `{ ... }` bodies, which are just block statements and are already implemented and tested. - Their `=> ...` bodies, which are formatted like assignments so need little additional implementation. This PR mostly just stitches that together, adds tests, and covers some of the bits that can't appear in those existing constructs: - The `external` modifier on function declarations. - Function names. - Default values. - The `var` and `final` modifiers on parameters. This PR does not do getters and setters. That will come later.
1 parent a866478 commit ba88de7

15 files changed

+420
-40
lines changed

lib/src/ast_extensions.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ extension ExpressionExtensions on Expression {
110110
/// ];
111111
/// ```
112112
bool get isDelimited => switch (this) {
113-
ParenthesizedExpression(:var expression) => expression.isDelimited,
113+
FunctionExpression() => true,
114114
ListLiteral() => true,
115115
MethodInvocation() => true,
116+
ParenthesizedExpression(:var expression) => expression.isDelimited,
116117
RecordLiteral() => true,
117118
SetOrMapLiteral() => true,
118119
SwitchExpression() => true,

lib/src/front_end/ast_node_visitor.dart

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,8 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
157157

158158
@override
159159
void visitBlockFunctionBody(BlockFunctionBody node) {
160-
throw UnimplementedError();
160+
functionBodyModifiers(node);
161+
visit(node.block);
161162
}
162163

163164
@override
@@ -300,9 +301,9 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
300301
void visitDefaultFormalParameter(DefaultFormalParameter node) {
301302
visit(node.parameter);
302303

303-
// TODO(tall): Implement default values when function declarations are
304-
// implemented.
305-
if (node.separator != null) throw UnimplementedError();
304+
if (node.separator case var separator?) {
305+
finishAssignment(separator, node.defaultValue!);
306+
}
306307
}
307308

308309
@override
@@ -335,7 +336,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
335336

336337
@override
337338
void visitEmptyFunctionBody(EmptyFunctionBody node) {
338-
throw UnimplementedError();
339+
token(node.semicolon);
339340
}
340341

341342
@override
@@ -360,7 +361,9 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
360361

361362
@override
362363
void visitExpressionFunctionBody(ExpressionFunctionBody node) {
363-
throw UnimplementedError();
364+
functionBodyModifiers(node);
365+
finishAssignment(node.functionDefinition, node.expression);
366+
token(node.semicolon);
364367
}
365368

366369
@override
@@ -522,17 +525,29 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
522525

523526
@override
524527
void visitFunctionDeclaration(FunctionDeclaration node) {
525-
throw UnimplementedError();
528+
modifier(node.externalKeyword);
529+
530+
Piece? returnType;
531+
if (node.returnType case var returnTypeNode?) {
532+
visit(returnTypeNode);
533+
returnType = pieces.split();
534+
}
535+
536+
// TODO(tall): Get or set keywords for getters and setters.
537+
if (node.propertyKeyword != null) throw UnimplementedError();
538+
token(node.name);
539+
540+
finishFunction(returnType, node.functionExpression);
526541
}
527542

528543
@override
529544
void visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
530-
throw UnimplementedError();
545+
visit(node.functionDeclaration);
531546
}
532547

533548
@override
534549
void visitFunctionExpression(FunctionExpression node) {
535-
throw UnimplementedError();
550+
finishFunction(null, node);
536551
}
537552

538553
@override
@@ -958,14 +973,9 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
958973
void visitSimpleFormalParameter(SimpleFormalParameter node) {
959974
startFormalParameter(node);
960975

961-
if (node.keyword != null) throw UnimplementedError();
962-
963-
// TODO(tall): When function declarations are implemented, test that the
964-
// formatter won't split after `var` or `final` in a parameter (with a
965-
// type or not).
966-
967976
if ((node.type, node.name) case (var type?, var name?)) {
968977
// Have both a type and name, so allow splitting between them.
978+
modifier(node.keyword);
969979
visit(type);
970980
var typePiece = pieces.split();
971981

@@ -975,6 +985,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
975985
pieces.give(VariablePiece(typePiece, [namePiece], hasType: true));
976986
} else {
977987
// Only one of name or type so just write whichever there is.
988+
modifier(node.keyword);
978989
visit(node.type);
979990
token(node.name);
980991
}

lib/src/front_end/piece_factory.dart

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,9 @@ mixin PieceFactory implements CommentWriter {
153153
visit(typeParameters);
154154
visit(parameters);
155155
token(question);
156+
var parametersPiece = pieces.take();
156157

157-
// Allow splitting after the return type.
158-
if (returnTypePiece != null) {
159-
var parametersPiece = pieces.take();
160-
pieces.give(FunctionTypePiece(returnTypePiece, parametersPiece));
161-
}
158+
pieces.give(FunctionPiece(returnTypePiece, parametersPiece));
162159
}
163160

164161
// TODO(tall): Generalize this to work with if elements too.
@@ -404,6 +401,14 @@ mixin PieceFactory implements CommentWriter {
404401
if (parameter.covariantKeyword != null) throw UnimplementedError();
405402
}
406403

404+
/// Handles the `async`, `sync*`, or `async*` modifiers on a function body.
405+
void functionBodyModifiers(FunctionBody body) {
406+
// The `async` or `sync` keyword.
407+
token(body.keyword);
408+
token(body.star);
409+
if (body.keyword != null) space();
410+
}
411+
407412
/// Creates a [Piece] for some code followed by an `=` and an expression in
408413
/// any place where an `=` appears:
409414
///
@@ -454,6 +459,29 @@ mixin PieceFactory implements CommentWriter {
454459
}
455460
}
456461

462+
/// Finishes writing a named function declaration or anonymous function
463+
/// expression after the return type (if any) and name (if any) has been
464+
/// written.
465+
void finishFunction(Piece? returnType, FunctionExpression function) {
466+
visit(function.typeParameters);
467+
visit(function.parameters);
468+
469+
Piece parameters;
470+
Piece? body;
471+
if (function.body case EmptyFunctionBody body) {
472+
// If the body is just `;`, then don't allow a space or split before the
473+
// semicolon by making it part of the parameters piece.
474+
token(body.semicolon);
475+
parameters = pieces.split();
476+
} else {
477+
parameters = pieces.split();
478+
visit(function.body);
479+
body = pieces.take();
480+
}
481+
482+
pieces.give(FunctionPiece(returnType, parameters, body));
483+
}
484+
457485
/// Writes an optional modifier that precedes other code.
458486
void modifier(Token? keyword) {
459487
token(keyword, after: space);

lib/src/piece/assign.dart

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,33 @@ class AssignPiece extends Piece {
7474
//
7575
// ```
7676
// var value = operand +
77-
// operand +
78-
// operand;
77+
// operand +
78+
// operand;
7979
// ```
8080
//
81-
// For now, we not implement this special case behavior. Once more of the
81+
// For now, we do not implement this special case behavior. Once more of the
8282
// language is implemented in the new back end and we can run the formatter
8383
// on a large corpus of code, we can try it out and see if the special case
8484
// behavior is worth it.
85+
//
86+
// If we don't do that, consider at least not adding another level of
87+
// indentation for subsequent operands in an infix operator chain. So prefer:
88+
//
89+
// ```
90+
// var value =
91+
// operand +
92+
// operand +
93+
// operand;
94+
// ```
95+
//
96+
// Over:
97+
//
98+
// ```
99+
// var value =
100+
// operand +
101+
// operand +
102+
// operand;
103+
// ```
85104

86105
@override
87106
List<State> get additionalStates =>

lib/src/piece/function.dart

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,54 @@
44
import '../back_end/code_writer.dart';
55
import 'piece.dart';
66

7-
/// Piece for a function type or function-typed parameter.
7+
/// Piece for a function declaration, function type, or function-typed
8+
/// parameter.
89
///
9-
/// Handles splitting between the return type and the rest of the function type.
10-
class FunctionTypePiece extends Piece {
11-
/// The return type annotation.
12-
final Piece _returnType;
10+
/// Handles splitting between the return type and the rest of the function.
11+
class FunctionPiece extends Piece {
12+
/// The return type annotation, if any.
13+
final Piece? _returnType;
1314

1415
/// The rest of the function type signature: name, type parameters,
1516
/// parameters, etc.
1617
final Piece _signature;
1718

18-
FunctionTypePiece(this._returnType, this._signature);
19+
/// If this is a function declaration with a (non-empty `;`) body, the body.
20+
final Piece? _body;
21+
22+
FunctionPiece(this._returnType, this._signature, [this._body]);
1923

2024
@override
21-
List<State> get additionalStates => const [State.split];
25+
List<State> get additionalStates => [if (_returnType != null) State.split];
2226

2327
@override
2428
void format(CodeWriter writer, State state) {
25-
// A split inside the return type forces splitting after the return type.
26-
writer.setAllowNewlines(state == State.split);
27-
writer.format(_returnType);
29+
if (_returnType case var returnType?) {
30+
// A split inside the return type forces splitting after the return type.
31+
writer.setAllowNewlines(state == State.split);
32+
33+
writer.format(returnType);
2834

29-
// A split in the type parameters or parameters does not force splitting
30-
// after the return type.
31-
writer.setAllowNewlines(true);
32-
writer.splitIf(state == State.split);
35+
// A split in the type parameters or parameters does not force splitting
36+
// after the return type.
37+
writer.setAllowNewlines(true);
38+
writer.splitIf(state == State.split);
39+
}
3340

3441
writer.format(_signature);
42+
if (_body case var body?) {
43+
writer.space();
44+
writer.format(body);
45+
}
3546
}
3647

3748
@override
3849
void forEachChild(void Function(Piece piece) callback) {
39-
callback(_returnType);
50+
if (_returnType case var returnType?) callback(returnType);
4051
callback(_signature);
52+
if (_body case var body?) callback(body);
4153
}
4254

4355
@override
44-
String toString() => 'FnType';
56+
String toString() => 'Fn';
4557
}

test/function/block_body.unit

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
40 columns |
2+
>>> Empty block body.
3+
f ( ) { }
4+
<<<
5+
f() {}
6+
>>> Collapse empty body.
7+
void main() { }
8+
<<<
9+
void main() {}
10+
>>>
11+
void main() {
12+
13+
14+
15+
}
16+
<<<
17+
void main() {}
18+
>>> Non-empty block body.
19+
f ( ) { body ; }
20+
<<<
21+
f() {
22+
body;
23+
}
24+
>>> Async.
25+
f ( ) async { }
26+
<<<
27+
f() async {}
28+
>>> Sync*.
29+
f ( ) sync * { }
30+
<<<
31+
f() sync* {}
32+
>>> Async*.
33+
f ( ) async * { }
34+
<<<
35+
f() async* {}

test/function/default_value.unit

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
40 columns |
2+
>>> Named parameter.
3+
f( { optional = null } ) {}
4+
<<<
5+
f({optional = null}) {}
6+
>>> Named parameter with old separator.
7+
### This syntax is no longer supported by new versions of Dart, but we want to
8+
### support formatting older versions if possible.
9+
f( { optional : 1 + 2 } ) {}
10+
<<<
11+
f({optional: 1 + 2}) {}
12+
>>> Optional positional parameter.
13+
f( [ optional = 1 + 2 ] ) {}
14+
<<<
15+
f([optional = 1 + 2]) {}
16+
>>> Split on positional default.
17+
doStuff([parameter = veryLongDefaultValueThatSplits, another =
18+
veryLongDefaultValue, third = alsoQuiteLongDefaultValue]) {}
19+
<<<
20+
doStuff([
21+
parameter =
22+
veryLongDefaultValueThatSplits,
23+
another = veryLongDefaultValue,
24+
third = alsoQuiteLongDefaultValue,
25+
]) {}
26+
>>> Split on named default.
27+
doStuff({parameter = veryLongDefaultValueThatSplits, another =
28+
veryLongDefaultValue, third = alsoAQuiteLongDefaultValue}) {}
29+
<<<
30+
doStuff({
31+
parameter =
32+
veryLongDefaultValueThatSplits,
33+
another = veryLongDefaultValue,
34+
third = alsoAQuiteLongDefaultValue,
35+
}) {}
36+
>>> Prefer block-like splitting for collection default values.
37+
function([param = [element, element, element, element]]) { body; }
38+
<<<
39+
function([
40+
param = [
41+
element,
42+
element,
43+
element,
44+
element,
45+
],
46+
]) {
47+
body;
48+
}

0 commit comments

Comments
 (0)