blob: 290daf3d4cf5e087455a8427f0595b33f15b54d6 [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import '../back_end/code_writer.dart';
import '../constants.dart';
import 'piece.dart';
/// A list of "clauses" where each clause starts with a keyword and has a
/// comma-separated list of items under it.
///
/// Used for `show` and `hide` combinators in import and export directives, and
/// `extends`, `implements`, and `with` clauses in type declarations.
///
/// Clauses can be chained on one line if they all fit, like:
///
/// import 'animals.dart' show Ant, Bat hide Cat, Dog;
///
/// Or can split before all of the clauses, like:
///
/// import 'animals.dart'
/// show Ant, Bat
/// hide Cat, Dog;
///
/// They can also split before every item in any of the clauses. If they do so,
/// then the clauses must split too. So these are allowed:
///
/// import 'animals.dart'
/// show
/// Ant,
/// Bat
/// hide Cat, Dog;
///
/// import 'animals.dart'
/// show Ant, Bat
/// hide
/// Cat,
/// Dog;
///
/// import 'animals.dart'
/// show
/// Ant,
/// Bat
/// hide
/// Cat,
/// Dog;
///
/// But these are not:
///
/// // Wrap list but not keyword:
/// import 'animals.dart' show
/// Ant,
/// Bat
/// hide Cat, Dog;
///
/// // Wrap one keyword but not both:
/// import 'animals.dart'
/// show Ant, Bat hide Cat, Dog;
///
/// import 'animals.dart' show Ant, Bat
/// hide Cat, Dog;
///
/// This ensures that when any wrapping occurs, the keywords are always at the
/// beginning of the line.
class ClausesPiece extends Piece {
/// State where we split between the clauses but not before the first one.
static const State _betweenClauses = State(1);
final List<ClausePiece> _clauses;
/// If `true`, then we're allowed to split between the clauses without
/// splitting before the first one too.
///
/// This is used for class declarations where the `extends` clauses is treated
/// a little specially because it's a deeper coupling to the class and so we
/// want it to stay on the top line even if the other clauses split, like:
///
/// class BaseClass extends Derived
/// implements OtherThing {
/// ...
/// }
final bool _allowLeadingClause;
ClausesPiece(this._clauses, {bool allowLeadingClause = false})
: _allowLeadingClause = allowLeadingClause;
@override
List<State> get additionalStates =>
[if (_allowLeadingClause) _betweenClauses, State.split];
@override
void format(CodeWriter writer, State state) {
writer.pushIndent(Indent.expression);
for (var clause in _clauses) {
if (_allowLeadingClause && clause == _clauses.first) {
// Before the leading clause, only split when in the fully split state.
// A split inside the first clause forces a split before the keyword.
writer.splitIf(state == State.split);
writer.pushAllowNewlines(state == State.split);
} else {
// For the other clauses (or if there is no leading one), split in the
// fully split state and any split inside and clause forces all of them
// to split.
writer.pushAllowNewlines(state != State.unsplit);
writer.splitIf(state != State.unsplit);
}
writer.format(clause);
writer.popAllowNewlines();
}
writer.popIndent();
}
@override
void forEachChild(void Function(Piece piece) callback) {
_clauses.forEach(callback);
}
}
/// A keyword followed by a comma-separated list of items described by that
/// keyword.
class ClausePiece extends Piece {
final Piece _keyword;
/// The list of items in the clause.
final List<Piece> _parts;
ClausePiece(this._keyword, this._parts);
@override
List<State> get additionalStates => const [State.split];
@override
void format(CodeWriter writer, State state) {
// If any of the parts inside the clause split, split the list.
writer.pushAllowNewlines(state != State.unsplit);
writer.pushIndent(Indent.expression);
writer.format(_keyword);
for (var part in _parts) {
writer.splitIf(state == State.split);
writer.format(part);
}
writer.popIndent();
writer.popAllowNewlines();
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_keyword);
_parts.forEach(callback);
}
}