Skip to content

Commit 03add6a

Browse files
authored
Format instance creation expressions (constructor calls). (#1307)
Format instance creation expressions (constructor calls). Simple constructor calls behave exactly like normal function calls and use the same formatting code for their argument list. Prefixed and/or named constructor calls are formatted sort of like method/property chains where they may split at the ".". We don't have full support for method/property chains yet, but this starts sketching that out enough to get the instance creation expression tests passing.
1 parent a8dd95c commit 03add6a

File tree

6 files changed

+166
-8
lines changed

6 files changed

+166
-8
lines changed

lib/src/ast_extensions.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,13 @@ extension ExpressionExtensions on Expression {
111111
/// ```
112112
bool get isDelimited => switch (this) {
113113
FunctionExpression() => true,
114+
InstanceCreationExpression() => true,
114115
ListLiteral() => true,
115116
MethodInvocation() => true,
116117
ParenthesizedExpression(:var expression) => expression.isDelimited,
117118
RecordLiteral() => true,
118119
SetOrMapLiteral() => true,
119120
SwitchExpression() => true,
120-
// TODO(tall): Instance creation expressions (`new` and `const`).
121121
_ => false,
122122
};
123123

lib/src/front_end/ast_node_visitor.dart

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:analyzer/source/line_info.dart';
99
import '../constants.dart';
1010
import '../dart_formatter.dart';
1111
import '../piece/block.dart';
12+
import '../piece/chain.dart';
1213
import '../piece/do_while.dart';
1314
import '../piece/for.dart';
1415
import '../piece/if.dart';
@@ -279,7 +280,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
279280

280281
@override
281282
void visitConstructorName(ConstructorName node) {
282-
throw UnimplementedError();
283+
assert(false, 'This node is handled by visitInstanceCreationExpression().');
283284
}
284285

285286
@override
@@ -618,7 +619,43 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
618619

619620
@override
620621
void visitInstanceCreationExpression(InstanceCreationExpression node) {
621-
throw UnimplementedError();
622+
token(node.keyword, after: space);
623+
624+
// If there is an import prefix and/or constructor name, then allow
625+
// splitting before the `.`. This doesn't look good, but is consistent with
626+
// constructor calls that don't have `new` or `const`. We allow splitting
627+
// in the latter because there is no way to distinguish syntactically
628+
// between a named constructor call and any other kind of method call or
629+
// property access.
630+
var operations = <Piece>[];
631+
632+
var constructor = node.constructorName;
633+
if (constructor.type.importPrefix case var importPrefix?) {
634+
token(importPrefix.name);
635+
operations.add(pieces.split());
636+
token(importPrefix.period);
637+
}
638+
639+
// The name of the type being constructed.
640+
var type = constructor.type;
641+
token(type.name2);
642+
visit(type.typeArguments);
643+
token(type.question);
644+
645+
// If this is a named constructor call, the name.
646+
if (constructor.name != null) {
647+
operations.add(pieces.split());
648+
token(constructor.period);
649+
visit(constructor.name);
650+
}
651+
652+
finishCall(node.argumentList);
653+
654+
// If there was a prefix or constructor name, then make a splittable piece.
655+
if (operations.isNotEmpty) {
656+
operations.add(pieces.take());
657+
pieces.give(ChainPiece(operations));
658+
}
622659
}
623660

624661
@override
@@ -729,11 +766,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<void>
729766

730767
visit(node.methodName);
731768
visit(node.typeArguments);
732-
733-
createList(
734-
leftBracket: node.argumentList.leftParenthesis,
735-
node.argumentList.arguments,
736-
rightBracket: node.argumentList.rightParenthesis);
769+
finishCall(node.argumentList);
737770
}
738771

739772
@override

lib/src/front_end/piece_factory.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,15 @@ mixin PieceFactory implements CommentWriter {
433433
isValueDelimited: rightHandSide.isDelimited));
434434
}
435435

436+
/// Writes the argument list part of a constructor, function, or method call
437+
/// after the name has been written.
438+
void finishCall(ArgumentList argumentList) {
439+
createList(
440+
leftBracket: argumentList.leftParenthesis,
441+
argumentList.arguments,
442+
rightBracket: argumentList.rightParenthesis);
443+
}
444+
436445
/// Writes the condition and updaters parts of a [ForParts] after the
437446
/// subclass's initializer clause has been written.
438447
void finishForParts(ForParts forLoopParts, DelimitedListBuilder partsList) {

lib/src/piece/chain.dart

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
import '../back_end/code_writer.dart';
5+
import '../constants.dart';
6+
import 'piece.dart';
7+
8+
// TODO(tall): This will probably become more elaborate when full method chains
9+
// with interesting argument lists are supported. Right now, it's just the
10+
// basics needed for instance creation expressions which may have method-like
11+
// `.` in them.
12+
13+
/// A dotted series of property access or method calls, like:
14+
///
15+
/// ```
16+
/// target.getter.method().another.method();
17+
/// ```
18+
///
19+
/// This piece handles splitting before the `.`.
20+
class ChainPiece extends Piece {
21+
/// The series of operations.
22+
///
23+
/// The first piece in this is the target, and the rest are operations.
24+
final List<Piece> _operations;
25+
26+
ChainPiece(this._operations);
27+
28+
@override
29+
List<State> get additionalStates => const [State.split];
30+
31+
@override
32+
void format(CodeWriter writer, State state) {
33+
if (state == State.unsplit) {
34+
writer.setAllowNewlines(false);
35+
} else {
36+
writer.setNesting(Indent.expression);
37+
}
38+
39+
for (var i = 0; i < _operations.length; i++) {
40+
if (i > 0) writer.splitIf(state == State.split, space: false);
41+
writer.format(_operations[i]);
42+
}
43+
}
44+
45+
@override
46+
void forEachChild(void Function(Piece piece) callback) {
47+
_operations.forEach(callback);
48+
}
49+
50+
@override
51+
String toString() => 'Chain';
52+
}

test/invocation/constructor.stmt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
40 columns |
2+
### Constructor invocations are handled identically to function calls, so just
3+
### test the basics and make sure that we handle the keywords correctly.
4+
>>> Empty argument list.
5+
new Foo ( ) ;
6+
<<<
7+
new Foo();
8+
>>> Inline arguments.
9+
new SomeType ( argument , another ) ;
10+
<<<
11+
new SomeType(argument, another);
12+
>>> Split argument list.
13+
const SomeType ( argument , another , third ) ;
14+
<<<
15+
const SomeType(
16+
argument,
17+
another,
18+
third,
19+
);
20+
>>> With type arguments.
21+
new Map < int , String > ( 1 , 2 , 3 );
22+
<<<
23+
new Map<int, String>(1, 2, 3);
24+
>>> Named constructor.
25+
new Thing . name ( argument ) ;
26+
<<<
27+
new Thing.name(argument);
28+
>>> Named constructor on class with type arguments.
29+
const List < int > . filled ( 1 , 2 );
30+
<<<
31+
const List<int>.filled(1, 2);
32+
>>> Prefixed.
33+
new prefix . TypeName ( argument ) ;
34+
<<<
35+
new prefix.TypeName(argument);
36+
>>> Prefix named constructor.
37+
const prefix . Thing . name ( argument ) ;
38+
<<<
39+
const prefix.Thing.name(argument);
40+
>>> Split at name.
41+
new VeryLongClassName.veryLongNamedConstructor();
42+
<<<
43+
new VeryLongClassName
44+
.veryLongNamedConstructor();
45+
>>> Split at name on prefixed named constructor.
46+
new prefix.VeryLongClassName.veryLongNamedConstructor();
47+
<<<
48+
new prefix
49+
.VeryLongClassName
50+
.veryLongNamedConstructor();

test/variable/local.stmt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ var longVariableName = veryLongFunctionName(argument);
173173
<<<
174174
var longVariableName =
175175
veryLongFunctionName(argument);
176+
>>> Prefer block-like splitting for constructor calls.
177+
var variableName = new Thing(argument, argument);
178+
<<<
179+
var variableName = new Thing(
180+
argument,
181+
argument,
182+
);
183+
>>> Prefer block-like splitting for const constructor calls.
184+
var variableName = const Thing(argument, argument);
185+
<<<
186+
var variableName = const Thing(
187+
argument,
188+
argument,
189+
);
176190
>>> Indent block if function name doesn't fit and arguments split.
177191
var longVariableName = veryLongFunctionName(argument, another);
178192
<<<

0 commit comments

Comments
 (0)