Skip to content

Commit d4db548

Browse files
authored
Generalize import combinator pieces to other clauses. (#1308)
Generalize import combinator pieces to other clauses. For imports and exports, there is logic around how to split and format the "show" and "hide" combinators. I plan to use the same logic for "extends", "implements", and "with" clauses in classes so this generalizes that code to not be specific to imports. It's also a little simpler and cleaner now.
1 parent 03add6a commit d4db548

File tree

5 files changed

+188
-211
lines changed

5 files changed

+188
-211
lines changed

lib/src/constants.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,4 @@ class Indent {
7979

8080
/// The ":" on a wrapped constructor initialization list.
8181
static const constructorInitializer = 4;
82-
83-
/// A split name in a show or hide combinator.
84-
static const combinatorName = 8;
8582
}

lib/src/front_end/piece_factory.dart

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

77
import '../ast_extensions.dart';
8+
import '../piece/adjacent.dart';
89
import '../piece/assign.dart';
910
import '../piece/block.dart';
11+
import '../piece/clause.dart';
1012
import '../piece/function.dart';
1113
import '../piece/if.dart';
12-
import '../piece/import.dart';
1314
import '../piece/infix.dart';
1415
import '../piece/list.dart';
1516
import '../piece/piece.dart';
@@ -224,9 +225,8 @@ mixin PieceFactory implements CommentWriter {
224225
token(keyword);
225226
space();
226227
visit(directive.uri);
227-
var directivePiece = pieces.take();
228+
var importPieces = [pieces.take()];
228229

229-
Piece? configurationsPiece;
230230
if (directive.configurations.isNotEmpty) {
231231
var configurations = <Piece>[];
232232
for (var configuration in directive.configurations) {
@@ -235,44 +235,49 @@ mixin PieceFactory implements CommentWriter {
235235
configurations.add(pieces.take());
236236
}
237237

238-
configurationsPiece = PostfixPiece(configurations);
238+
importPieces.add(PostfixPiece(configurations));
239239
}
240240

241-
Piece? asClause;
242241
if (asKeyword != null) {
243242
pieces.split();
244243
token(deferredKeyword, after: space);
245244
token(asKeyword);
246245
space();
247246
visit(prefix);
248-
asClause = PostfixPiece([pieces.take()]);
247+
importPieces.add(PostfixPiece([pieces.take()]));
249248
}
250249

251-
var combinators = <ImportCombinator>[];
252-
for (var combinatorNode in directive.combinators) {
253-
pieces.split();
254-
token(combinatorNode.keyword);
255-
var combinator = ImportCombinator(pieces.take());
256-
combinators.add(combinator);
257-
258-
switch (combinatorNode) {
259-
case HideCombinator(hiddenNames: var names):
260-
case ShowCombinator(shownNames: var names):
261-
for (var name in names) {
262-
pieces.split();
263-
token(name.token);
264-
commaAfter(name);
265-
combinator.names.add(pieces.take());
266-
}
267-
default:
268-
throw StateError('Unknown combinator type $combinatorNode.');
250+
if (directive.combinators.isNotEmpty) {
251+
var combinators = <ClausePiece>[];
252+
for (var combinatorNode in directive.combinators) {
253+
pieces.split();
254+
token(combinatorNode.keyword);
255+
var combinatorKeyword = pieces.split();
256+
257+
switch (combinatorNode) {
258+
case HideCombinator(hiddenNames: var names):
259+
case ShowCombinator(shownNames: var names):
260+
var parts = <Piece>[];
261+
for (var name in names) {
262+
pieces.split();
263+
token(name.token);
264+
commaAfter(name);
265+
parts.add(pieces.take());
266+
}
267+
268+
var combinator = ClausePiece(combinatorKeyword, parts);
269+
combinators.add(combinator);
270+
default:
271+
throw StateError('Unknown combinator type $combinatorNode.');
272+
}
269273
}
274+
275+
importPieces.add(ClausesPiece(combinators));
270276
}
271277

272278
token(directive.semicolon);
273279

274-
pieces.give(ImportPiece(
275-
directivePiece, configurationsPiece, asClause, combinators));
280+
pieces.give(AdjacentPiece(importPieces));
276281
}
277282

278283
/// Creates a single infix operation.

lib/src/piece/adjacent.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 'piece.dart';
6+
7+
/// A simple piece that just writes its child pieces one after the other.
8+
class AdjacentPiece extends Piece {
9+
final List<Piece> _pieces;
10+
11+
AdjacentPiece(this._pieces);
12+
13+
@override
14+
void format(CodeWriter writer, State state) {
15+
for (var piece in _pieces) {
16+
writer.format(piece);
17+
}
18+
}
19+
20+
@override
21+
void forEachChild(void Function(Piece piece) callback) {
22+
_pieces.forEach(callback);
23+
}
24+
25+
@override
26+
String toString() => 'Adjacent';
27+
}

lib/src/piece/clause.dart

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
/// A list of "clauses" where each clause starts with a keyword and has a
9+
/// comma-separated list of items under it.
10+
///
11+
/// Used for `show` and `hide` combinators in import and export directives, and
12+
/// `extends`, `implements`, and `with` clauses in type declarations.
13+
///
14+
/// Clauses can be chained on one line if they all fit, like:
15+
///
16+
/// ```
17+
/// import 'animals.dart' show Ant, Bat hide Cat, Dog;
18+
/// ```
19+
///
20+
/// Or can split before all of the clauses, like:
21+
///
22+
/// ```
23+
/// import 'animals.dart'
24+
/// show Ant, Bat
25+
/// hide Cat, Dog;
26+
/// ```
27+
///
28+
/// They can also split before every item in any of the clauses. If they do so,
29+
/// then the clauses must split too. So these are allowed:
30+
///
31+
/// ```
32+
/// import 'animals.dart'
33+
/// show
34+
/// Ant,
35+
/// Bat
36+
/// hide Cat, Dog;
37+
///
38+
/// import 'animals.dart'
39+
/// show Ant, Bat
40+
/// hide
41+
/// Cat,
42+
/// Dog;
43+
///
44+
/// import 'animals.dart'
45+
/// show
46+
/// Ant,
47+
/// Bat
48+
/// hide
49+
/// Cat,
50+
/// Dog;
51+
/// ```
52+
///
53+
/// But these are not:
54+
///
55+
/// ```
56+
/// // Wrap list but not keyword:
57+
/// import 'animals.dart' show
58+
/// Ant,
59+
/// Bat
60+
/// hide Cat, Dog;
61+
///
62+
/// // Wrap one keyword but not both:
63+
/// import 'animals.dart'
64+
/// show Ant, Bat hide Cat, Dog;
65+
///
66+
/// import 'animals.dart' show Ant, Bat
67+
/// hide Cat, Dog;
68+
/// ```
69+
///
70+
/// This ensures that when any wrapping occurs, the keywords are always at the
71+
/// beginning of the line.
72+
class ClausesPiece extends Piece {
73+
final List<ClausePiece> _clauses;
74+
75+
ClausesPiece(this._clauses);
76+
77+
@override
78+
List<State> get additionalStates => const [State.split];
79+
80+
@override
81+
void format(CodeWriter writer, State state) {
82+
// If any of the lists inside any of the clauses split, split at the
83+
// keywords too.
84+
writer.setAllowNewlines(state == State.split);
85+
for (var clause in _clauses) {
86+
writer.splitIf(state == State.split, indent: Indent.expression);
87+
writer.format(clause);
88+
}
89+
}
90+
91+
@override
92+
void forEachChild(void Function(Piece piece) callback) {
93+
_clauses.forEach(callback);
94+
}
95+
96+
@override
97+
String toString() => 'Clauses';
98+
}
99+
100+
/// A keyword followed by a comma-separated list of items described by that
101+
/// keyword.
102+
class ClausePiece extends Piece {
103+
final Piece _keyword;
104+
105+
/// The list of items in the clause.
106+
final List<Piece> _parts;
107+
108+
ClausePiece(this._keyword, this._parts);
109+
110+
@override
111+
List<State> get additionalStates => const [State.split];
112+
113+
@override
114+
void format(CodeWriter writer, State state) {
115+
writer.format(_keyword);
116+
for (var part in _parts) {
117+
writer.splitIf(state == State.split, indent: Indent.expression);
118+
writer.format(part);
119+
}
120+
}
121+
122+
@override
123+
void forEachChild(void Function(Piece piece) callback) {
124+
callback(_keyword);
125+
_parts.forEach(callback);
126+
}
127+
128+
@override
129+
String toString() => 'Clause';
130+
}

0 commit comments

Comments
 (0)