Skip to content

Commit 2695039

Browse files
authored
If a class's type parameters split, force the clauses to split too. (#1511)
I think it looks weird if the `extends`, etc. clauses hang immediately after the `>` when the type parameters split. Since import/export directives use the same piece, this nominally affects them too but in practice it's extremely rare for there to be a newline in an import before any other clauses. While I was at it, I cleaned up the way we build a piece tree for directives. That's some of the oldest code in the new formatter and was more complex than it needed to be. There was no need to use both an InfixPiece and a ClausesPiece since they do basically the same thing.
1 parent a1fcb10 commit 2695039

File tree

7 files changed

+89
-63
lines changed

7 files changed

+89
-63
lines changed

lib/src/front_end/piece_factory.dart

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -805,55 +805,48 @@ mixin PieceFactory {
805805
void writeImport(NamespaceDirective directive, Token keyword,
806806
{Token? deferredKeyword, Token? asKeyword, SimpleIdentifier? prefix}) {
807807
pieces.withMetadata(directive.metadata, () {
808-
if (directive.configurations.isEmpty && asKeyword == null) {
809-
// If there are no configurations or prefix (the common case), just
810-
// write the import directly inline.
808+
// Build a piece for the directive itself.
809+
var directivePiece = pieces.build(() {
811810
pieces.token(keyword);
812811
pieces.space();
813812
pieces.visit(directive.uri);
814-
} else {
815-
// Otherwise, allow splitting between the configurations and prefix.
816-
var sections = [
817-
pieces.build(() {
818-
pieces.token(keyword);
819-
pieces.space();
820-
pieces.visit(directive.uri);
821-
})
822-
];
823-
824-
for (var configuration in directive.configurations) {
825-
sections.add(nodePiece(configuration));
826-
}
813+
});
827814

828-
if (asKeyword != null) {
829-
sections.add(pieces.build(() {
830-
pieces.token(deferredKeyword, spaceAfter: true);
831-
pieces.token(asKeyword);
832-
pieces.space();
833-
pieces.visit(prefix!);
834-
}));
835-
}
815+
// Include any `if` clauses.
816+
var clauses = <Piece>[];
817+
for (var configuration in directive.configurations) {
818+
clauses.add(nodePiece(configuration));
819+
}
836820

837-
pieces.add(InfixPiece(const [], sections));
821+
// Include the `as` clause.
822+
if (asKeyword != null) {
823+
clauses.add(pieces.build(() {
824+
pieces.token(deferredKeyword, spaceAfter: true);
825+
pieces.token(asKeyword);
826+
pieces.space();
827+
pieces.visit(prefix!);
828+
}));
838829
}
839830

840-
if (directive.combinators.isNotEmpty) {
841-
var combinators = <Piece>[];
842-
for (var combinatorNode in directive.combinators) {
843-
switch (combinatorNode) {
844-
case HideCombinator(hiddenNames: var names):
845-
case ShowCombinator(shownNames: var names):
846-
combinators.add(InfixPiece(const [], [
847-
tokenPiece(combinatorNode.keyword),
848-
for (var name in names)
849-
tokenPiece(name.token, commaAfter: true),
850-
]));
851-
default:
852-
throw StateError('Unknown combinator type $combinatorNode.');
853-
}
831+
// Include the `show` and `hide` clauses.
832+
for (var combinatorNode in directive.combinators) {
833+
switch (combinatorNode) {
834+
case HideCombinator(hiddenNames: var names):
835+
case ShowCombinator(shownNames: var names):
836+
clauses.add(InfixPiece(const [], [
837+
tokenPiece(combinatorNode.keyword),
838+
for (var name in names) tokenPiece(name.token, commaAfter: true),
839+
]));
840+
default:
841+
throw StateError('Unknown combinator type $combinatorNode.');
854842
}
843+
}
855844

856-
pieces.add(ClausePiece(combinators));
845+
// If there are clauses, include them.
846+
if (clauses.isNotEmpty) {
847+
pieces.add(ClausePiece(directivePiece, clauses));
848+
} else {
849+
pieces.add(directivePiece);
857850
}
858851

859852
pieces.token(directive.semicolon);
@@ -1245,16 +1238,12 @@ mixin PieceFactory {
12451238
[if (nativeClause.name case var name?) name]);
12461239
}
12471240

1248-
ClausePiece? clausesPiece;
12491241
if (clauses.isNotEmpty) {
1250-
clausesPiece = ClausePiece(clauses,
1242+
header = ClausePiece(header, clauses,
12511243
allowLeadingClause: extendsClause != null || onClause != null);
12521244
}
12531245

1254-
var bodyPiece = body();
1255-
1256-
pieces
1257-
.add(TypePiece(header, clausesPiece, bodyPiece, bodyType: bodyType));
1246+
pieces.add(TypePiece(header, body(), bodyType: bodyType));
12581247
});
12591248
}
12601249

lib/src/piece/clause.dart

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ class ClausePiece extends Piece {
6565
/// State where we split between the clauses but not before the first one.
6666
static const State _betweenClauses = State(1);
6767

68+
/// The leading construct the clauses are applied to: a class declaration,
69+
/// import directive, etc.
70+
final Piece _header;
71+
6872
final List<Piece> _clauses;
6973

7074
/// If `true`, then we're allowed to split between the clauses without
@@ -80,7 +84,7 @@ class ClausePiece extends Piece {
8084
/// }
8185
final bool _allowLeadingClause;
8286

83-
ClausePiece(this._clauses, {bool allowLeadingClause = false})
87+
ClausePiece(this._header, this._clauses, {bool allowLeadingClause = false})
8488
: _allowLeadingClause = allowLeadingClause && _clauses.length > 1;
8589

8690
@override
@@ -89,7 +93,10 @@ class ClausePiece extends Piece {
8993

9094
@override
9195
bool allowNewlineInChild(State state, Piece child) {
92-
if (_allowLeadingClause && child == _clauses.first) {
96+
if (child == _header) {
97+
// If the header splits, force the clauses to split too.
98+
return state == State.split;
99+
} else if (_allowLeadingClause && child == _clauses.first) {
93100
// A split inside the first clause forces a split before the keyword.
94101
return state == State.split;
95102
} else {
@@ -101,6 +108,8 @@ class ClausePiece extends Piece {
101108

102109
@override
103110
void format(CodeWriter writer, State state) {
111+
writer.format(_header);
112+
104113
writer.pushIndent(Indent.expression);
105114

106115
for (var clause in _clauses) {
@@ -123,6 +132,7 @@ class ClausePiece extends Piece {
123132

124133
@override
125134
void forEachChild(void Function(Piece piece) callback) {
135+
callback(_header);
126136
_clauses.forEach(callback);
127137
}
128138
}

lib/src/piece/type.dart

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,23 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44
import '../back_end/code_writer.dart';
5-
import 'clause.dart';
65
import 'piece.dart';
76

87
/// Piece for a type declaration with a body containing members.
98
///
109
/// Used for class, enum, and extension declarations.
1110
class TypePiece extends Piece {
12-
/// The leading keywords and modifiers, type name, and type parameters.
11+
/// The leading keywords and modifiers, type name, type parameters, and any
12+
/// other `extends`, `with`, etc. clauses.
1313
final Piece _header;
1414

15-
/// The `extends`, `with`, and/or `implements` clauses, if there are any.
16-
final ClausePiece? _clauses;
17-
1815
/// The `native` clause, if any, and the type body.
1916
final Piece _body;
2017

2118
/// What kind of body the type has.
2219
final TypeBodyType _bodyType;
2320

24-
TypePiece(this._header, this._clauses, this._body,
25-
{required TypeBodyType bodyType})
21+
TypePiece(this._header, this._body, {required TypeBodyType bodyType})
2622
: _bodyType = bodyType;
2723

2824
@override
@@ -47,18 +43,13 @@ class TypePiece extends Piece {
4743
@override
4844
void format(CodeWriter writer, State state) {
4945
writer.format(_header);
50-
if (_clauses case var clauses?) {
51-
writer.format(clauses);
52-
}
53-
5446
if (_bodyType != TypeBodyType.semicolon) writer.space();
5547
writer.format(_body);
5648
}
5749

5850
@override
5951
void forEachChild(void Function(Piece piece) callback) {
6052
callback(_header);
61-
if (_clauses case var clauses?) callback(clauses);
6253
callback(_body);
6354
}
6455
}

test/tall/declaration/class_clause.unit

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,4 +251,21 @@ class C extends A
251251
B<
252252
LongTypeArgument,
253253
AnotherLongType
254-
> {}
254+
> {}
255+
>>> Split in class type parameters forces clause to split.
256+
class C<LongTypeParameter, AnotherLongType> extends Other {}
257+
<<<
258+
class C<
259+
LongTypeParameter,
260+
AnotherLongType
261+
>
262+
extends Other {}
263+
>>> Split in class type parameters forces clauses to split.
264+
class C<LongTypeParameter, AnotherLongType> extends Other with Mixin {}
265+
<<<
266+
class C<
267+
LongTypeParameter,
268+
AnotherLongType
269+
>
270+
extends Other
271+
with Mixin {}

test/tall/declaration/extension.unit

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ extension Extension<LongTypeParameter, Another> on BaseClass {}
3535
extension Extension<
3636
LongTypeParameter,
3737
Another
38-
> on BaseClass {}
38+
>
39+
on BaseClass {}
3940
>>> Unnamed.
4041
extension on String {}
4142
<<<

test/tall/declaration/extension_type.unit

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ extension type LongExtensionType(LongTypeName a) implements Something {
4848
<<<
4949
extension type LongExtensionType(
5050
LongTypeName a,
51-
) implements Something {
51+
)
52+
implements Something {
5253
method() {
5354
;
5455
}

test/tall/regression/other/misc.unit

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,21 @@ SomeVeryLongReturnType someFunctionName(
1919
})
2020
.then(________traverseDeps_depender__deps_);
2121
}
22+
}
23+
>>> Split in long type parameter clause.
24+
abstract class StreamNotifierProviderBase<
25+
NotifierT extends AsyncNotifierBase<T>,
26+
T
27+
> extends ProviderWithNotifier<AsyncValue<T>, NotifierT>
28+
with FutureModifier<T> {
29+
// ...
30+
}
31+
<<<
32+
abstract class StreamNotifierProviderBase<
33+
NotifierT extends AsyncNotifierBase<T>,
34+
T
35+
>
36+
extends ProviderWithNotifier<AsyncValue<T>, NotifierT>
37+
with FutureModifier<T> {
38+
// ...
2239
}

0 commit comments

Comments
 (0)