Skip to content

Commit 8d7d86e

Browse files
authored
Format metadata annotations. (#1386)
Format metadata annotations. This handles metadata everywhere except on for loop variables. I'll do that later after patterns in for loops are supported. Since there were some metadata tests on pattern variable declarations, this also includes a trivial implementation of that. There's a TODO to revisit it and add real tests, but it's enough for the metadata tests. Figuring out where to put the metadata-handling code was a little tricky since so many places in the language can have metadata. I ultimately decided to put it into AdjacentBuilder. Every place that was working with metadata also had one of those builders in play, so that made it easy to add a simple call to `b.metadata()` and get the metadata working. It does mean that AdjacentBuilder isn't strictly about adjacent pieces anymore, but I think that's OK. In practice, it's used mostly as a general "build pieces out of smaller things" builder.
1 parent 50e1248 commit 8d7d86e

File tree

14 files changed

+767
-122
lines changed

14 files changed

+767
-122
lines changed

lib/src/front_end/adjacent_builder.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,43 @@ import 'package:analyzer/dart/ast/ast.dart';
55
import 'package:analyzer/dart/ast/token.dart';
66

77
import '../piece/adjacent.dart';
8+
import '../piece/list.dart';
89
import '../piece/piece.dart';
10+
import 'delimited_list_builder.dart';
911
import 'piece_factory.dart';
12+
import 'sequence_builder.dart';
1013

1114
/// Incrementally builds an [AdjacentPiece].
15+
///
16+
/// Can also be used to attach metadata annotations to the [Piece] being built.
1217
class AdjacentBuilder {
1318
final PieceFactory _visitor;
1419

20+
final List<Piece> _metadataPieces = [];
21+
22+
/// Whether the annotations added by a call to [metadata] should be allowed
23+
/// on the same line as the code they annotate, or whether a mandatory
24+
/// newline should be inserted after each annotation.
25+
bool _isMetadataInline = false;
26+
1527
/// The series of adjacent pieces.
1628
final List<Piece> _pieces = [];
1729

1830
AdjacentBuilder(this._visitor);
1931

32+
/// Creates pieces for all of the annotations in [metadata].
33+
///
34+
/// When this builder is built, if there are any annotations, the returned
35+
/// Piece will contain the annotation pieces followed by the [AdjacentPiece]
36+
/// being built.
37+
void metadata(List<Annotation> metadata, {bool inline = false}) {
38+
_isMetadataInline = inline;
39+
40+
for (var annotation in metadata) {
41+
_metadataPieces.add(_visitor.nodePiece(annotation));
42+
}
43+
}
44+
2045
/// Yields a new piece containing all of the pieces added to or created by
2146
/// this builder. The caller must ensure it doesn't build an empty piece.
2247
///
@@ -28,6 +53,36 @@ class AdjacentBuilder {
2853
var result = _flattenPieces();
2954
_pieces.clear();
3055

56+
// If there is metadata, wrap the AdjacentPiece in another piece containing
57+
// the annotations first.
58+
if (_metadataPieces.isNotEmpty) {
59+
if (_isMetadataInline) {
60+
var list = DelimitedListBuilder(
61+
_visitor,
62+
const ListStyle(
63+
commas: Commas.none,
64+
spaceWhenUnsplit: true,
65+
));
66+
67+
for (var piece in _metadataPieces) {
68+
list.add(piece);
69+
}
70+
71+
list.add(result);
72+
result = list.build();
73+
} else {
74+
var sequence = SequenceBuilder(_visitor);
75+
for (var piece in _metadataPieces) {
76+
sequence.add(piece);
77+
}
78+
79+
sequence.add(result);
80+
result = sequence.build(forceSplit: true);
81+
}
82+
83+
_metadataPieces.clear();
84+
}
85+
3186
return result;
3287
}
3388

lib/src/front_end/ast_node_visitor.dart

Lines changed: 71 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,14 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
117117

118118
@override
119119
Piece visitAnnotation(Annotation node) {
120-
throw UnimplementedError();
120+
return buildPiece((b) {
121+
b.token(node.atSign);
122+
b.visit(node.name);
123+
b.visit(node.typeArguments);
124+
b.token(node.period);
125+
b.visit(node.constructorName);
126+
b.visit(node.arguments);
127+
});
121128
}
122129

123130
@override
@@ -360,6 +367,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
360367
@override
361368
Piece visitConstructorDeclaration(ConstructorDeclaration node) {
362369
var header = buildPiece((b) {
370+
b.metadata(node.metadata);
363371
b.modifier(node.externalKeyword);
364372
b.modifier(node.constKeyword);
365373
b.modifier(node.factoryKeyword);
@@ -500,7 +508,8 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
500508

501509
@override
502510
Piece visitEnumDeclaration(EnumDeclaration node) {
503-
if (node.metadata.isNotEmpty) throw UnimplementedError();
511+
var metadataBuilder = AdjacentBuilder(this);
512+
metadataBuilder.metadata(node.metadata);
504513

505514
var header = buildPiece((b) {
506515
b.token(node.enumKeyword);
@@ -512,48 +521,53 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
512521
if (node.members.isEmpty) {
513522
// If there are no members, format the constants like a delimited list.
514523
// This keeps the enum declaration on one line if it fits.
524+
// TODO(tall): The old style preserves blank lines and newlines between
525+
// enum values. A newline will also force the enum to split even if it
526+
// would otherwise fit. Do we want to do that with the new style too?
515527
var builder = DelimitedListBuilder(
516528
this,
517529
const ListStyle(
518530
spaceWhenUnsplit: true, splitListIfBeforeSplits: true));
519531
builder.leftBracket(node.leftBracket, preceding: header);
520532
node.constants.forEach(builder.visit);
521533
builder.rightBracket(semicolon: node.semicolon, node.rightBracket);
522-
return builder.build();
534+
metadataBuilder.add(builder.build());
523535
} else {
524-
var builder = AdjacentBuilder(this);
525-
builder.add(header);
526-
builder.space();
527-
528-
// If there are members, format it like a block where each constant and
529-
// member is on its own line.
530-
var sequence = SequenceBuilder(this);
531-
sequence.leftBracket(node.leftBracket);
536+
metadataBuilder.add(buildPiece((b) {
537+
b.add(header);
538+
b.space();
532539

533-
for (var constant in node.constants) {
534-
sequence.addCommentsBefore(constant.firstNonCommentToken);
535-
sequence.add(createEnumConstant(constant,
536-
hasMembers: true,
537-
isLastConstant: constant == node.constants.last,
538-
semicolon: node.semicolon));
539-
}
540+
// If there are members, format it like a block where each constant and
541+
// member is on its own line.
542+
var members = SequenceBuilder(this);
543+
members.leftBracket(node.leftBracket);
544+
545+
for (var constant in node.constants) {
546+
members.addCommentsBefore(constant.firstNonCommentToken);
547+
members.add(createEnumConstant(constant,
548+
hasMembers: true,
549+
isLastConstant: constant == node.constants.last,
550+
semicolon: node.semicolon));
551+
}
540552

541-
// Insert a blank line between the constants and members.
542-
sequence.addBlank();
553+
// Insert a blank line between the constants and members.
554+
members.addBlank();
543555

544-
for (var node in node.members) {
545-
sequence.visit(node);
556+
for (var node in node.members) {
557+
members.visit(node);
546558

547-
// If the node has a non-empty braced body, then require a blank line
548-
// between it and the next node.
549-
if (node.hasNonEmptyBody) sequence.addBlank();
550-
}
559+
// If the node has a non-empty braced body, then require a blank line
560+
// between it and the next node.
561+
if (node.hasNonEmptyBody) members.addBlank();
562+
}
551563

552-
sequence.rightBracket(node.rightBracket);
564+
members.rightBracket(node.rightBracket);
553565

554-
builder.add(sequence.build());
555-
return builder.build();
566+
b.add(members.build());
567+
}));
556568
}
569+
570+
return metadataBuilder.build();
557571
}
558572

559573
@override
@@ -606,6 +620,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
606620
@override
607621
Piece visitFieldDeclaration(FieldDeclaration node) {
608622
return buildPiece((b) {
623+
b.metadata(node.metadata);
609624
b.modifier(node.externalKeyword);
610625
b.modifier(node.staticKeyword);
611626
b.modifier(node.abstractKeyword);
@@ -739,6 +754,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
739754
@override
740755
Piece visitFunctionDeclaration(FunctionDeclaration node) {
741756
return createFunction(
757+
metadata: node.metadata,
742758
modifiers: [node.externalKeyword],
743759
returnType: node.returnType,
744760
propertyKeyword: node.propertyKeyword,
@@ -784,11 +800,11 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
784800

785801
@override
786802
Piece visitFunctionTypeAlias(FunctionTypeAlias node) {
787-
if (node.metadata.isNotEmpty) throw UnimplementedError();
788-
789803
return buildPiece((b) {
804+
b.metadata(node.metadata);
790805
b.token(node.typedefKeyword);
791806
b.space();
807+
b.visit(node.returnType, spaceAfter: true);
792808
b.token(node.name);
793809
b.visit(node.typeParameters);
794810
b.visit(node.parameters);
@@ -815,9 +831,8 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
815831

816832
@override
817833
Piece visitGenericTypeAlias(GenericTypeAlias node) {
818-
if (node.metadata.isNotEmpty) throw UnimplementedError();
819-
820834
return buildPiece((b) {
835+
b.metadata(node.metadata);
821836
b.token(node.typedefKeyword);
822837
b.space();
823838
b.token(node.name);
@@ -1088,7 +1103,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
10881103
@override
10891104
Piece visitLibraryDirective(LibraryDirective node) {
10901105
return buildPiece((b) {
1091-
createDirectiveMetadata(node);
1106+
b.metadata(node.metadata);
10921107
b.token(node.libraryKeyword);
10931108
b.visit(node.name2, spaceBefore: true);
10941109
b.token(node.semicolon);
@@ -1177,6 +1192,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
11771192
@override
11781193
Piece visitMethodDeclaration(MethodDeclaration node) {
11791194
return createFunction(
1195+
metadata: node.metadata,
11801196
modifiers: [node.externalKeyword, node.modifierKeyword],
11811197
returnType: node.returnType,
11821198
propertyKeyword: node.operatorKeyword ?? node.propertyKeyword,
@@ -1309,7 +1325,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
13091325
@override
13101326
Piece visitPartDirective(PartDirective node) {
13111327
return buildPiece((b) {
1312-
createDirectiveMetadata(node);
1328+
b.metadata(node.metadata);
13131329
b.token(node.partKeyword);
13141330
b.space();
13151331
b.visit(node.uri);
@@ -1320,8 +1336,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
13201336
@override
13211337
Piece visitPartOfDirective(PartOfDirective node) {
13221338
return buildPiece((b) {
1323-
createDirectiveMetadata(node);
1324-
1339+
b.metadata(node.metadata);
13251340
b.token(node.partKeyword);
13261341
b.space();
13271342
b.token(node.ofKeyword);
@@ -1359,13 +1374,27 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
13591374

13601375
@override
13611376
Piece visitPatternVariableDeclaration(PatternVariableDeclaration node) {
1362-
throw UnimplementedError();
1377+
// TODO(tall): This is just a basic implementation for the metadata tests.
1378+
// It still needs a full implementation and tests.
1379+
return buildPiece((b) {
1380+
b.metadata(node.metadata);
1381+
b.token(node.keyword);
1382+
b.space();
1383+
b.visit(node.pattern);
1384+
b.space();
1385+
b.token(node.equals);
1386+
b.space();
1387+
b.visit(node.expression);
1388+
});
13631389
}
13641390

13651391
@override
13661392
Piece visitPatternVariableDeclarationStatement(
13671393
PatternVariableDeclarationStatement node) {
1368-
throw UnimplementedError();
1394+
return buildPiece((b) {
1395+
b.visit(node.declaration);
1396+
b.token(node.semicolon);
1397+
});
13691398
}
13701399

13711400
@override
@@ -1740,6 +1769,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
17401769
@override
17411770
Piece visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
17421771
return buildPiece((b) {
1772+
b.metadata(node.metadata);
17431773
b.modifier(node.externalKeyword);
17441774
b.visit(node.variables);
17451775
b.token(node.semicolon);
@@ -1759,6 +1789,7 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
17591789
@override
17601790
Piece visitTypeParameter(TypeParameter node) {
17611791
return buildPiece((b) {
1792+
b.metadata(node.metadata, inline: true);
17621793
b.token(node.name);
17631794
if (node.bound case var bound?) {
17641795
b.space();
@@ -1782,10 +1813,8 @@ class AstNodeVisitor extends ThrowingAstVisitor<Piece> with PieceFactory {
17821813

17831814
@override
17841815
Piece visitVariableDeclarationList(VariableDeclarationList node) {
1785-
// TODO(tall): Format metadata.
1786-
if (node.metadata.isNotEmpty) throw UnimplementedError();
1787-
17881816
var header = buildPiece((b) {
1817+
b.metadata(node.metadata);
17891818
b.modifier(node.lateKeyword);
17901819
b.modifier(node.keyword);
17911820

0 commit comments

Comments
 (0)