blob: 9af85e2621fe4cfa9c246206cfe49c5f3f31b88f [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 'piece.dart';
/// A piece for the `for (...)` part of a for statement or element.
final class ForPiece extends Piece {
/// The `for` keyword.
final Piece _forKeyword;
/// The part inside `( ... )`, including the parentheses themselves, at the
/// header of a for statement.
final Piece _parts;
/// Whether the contents of the parentheses in the `for (...)` should be
/// expression indented or not.
///
/// This is usually not necessary because the contents will either be a
/// [ListPiece] which adds its own block indentation, or an [AssignPiece]
/// which indents as necessary. But in the rare case the for-parts is a
/// variable or pattern variable declaration with metadata that splits, we
/// need to ensure that the metadata is indented, as in:
///
/// for (@LongAnnotation
/// @AnotherAnnotation
/// var element in list) { ... }
final bool _indent;
ForPiece(this._forKeyword, this._parts, {required bool indent})
: _indent = indent;
@override
void format(CodeWriter writer, State state) {
writer.format(_forKeyword);
writer.space();
if (_indent) writer.pushCollapsibleIndent();
writer.format(_parts);
if (_indent) writer.popIndent();
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_forKeyword);
callback(_parts);
}
}
/// A piece for the `<variable> in <expression>` part of a for-in loop.
///
/// Can be formatted two ways:
///
/// [State.unsplit] No split at all:
///
/// for (var x in y) ...
///
/// This state also allows splitting the sequence expression if it's block
/// shaped:
///
/// for (var i in [
/// element1,
/// element2,
/// element3,
/// ];
///
/// [State.split] Split at the `in` operator and allow expression splitting on
/// either side. Allows:
///
/// for (var (longVariable &&
/// anotherVariable)
/// in longOperand +
/// anotherOperand) {
/// ...
/// }
abstract base class ForInPiece extends Piece {
/// The variable or pattern initialized with each loop iteration.
final Piece _variable;
/// The `in` keyword followed by the sequence expression.
final Piece _sequence;
factory ForInPiece(
Piece variable,
Piece sequence, {
bool canBlockSplitSequence = false,
required bool is3Dot7,
}) {
if (is3Dot7) {
return _ForInPiece3Dot7(
variable,
sequence,
canBlockSplitSequence: canBlockSplitSequence,
);
} else {
return _ForInPiece(variable, sequence);
}
}
ForInPiece._(this._variable, this._sequence);
@override
List<State> get additionalStates => const [State.split];
@override
void format(CodeWriter writer, State state) {
// When splitting at `in`, both operands may split or not and will be
// indented if they do.
if (state == State.split) writer.pushIndent(Indent.expression);
writer.format(_variable);
writer.splitIf(state == State.split);
writer.format(_sequence);
if (state == State.split) writer.popIndent();
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_variable);
callback(_sequence);
}
}
/// A [ForInPiece] subclass for 3.8 and later style.
final class _ForInPiece extends ForInPiece {
_ForInPiece(super._variable, super._sequence) : super._();
@override
Set<Shape> allowedChildShapes(State state, Piece child) => switch (state) {
// Always allow block-splitting the sequence if it supports it.
State.unsplit when child == _sequence => const {Shape.inline, Shape.block},
State.unsplit => Shape.onlyInline,
_ => Shape.all,
};
}
/// A [ForInPiece] subclass for 3.7 style.
final class _ForInPiece3Dot7 extends ForInPiece {
/// If `true` then the sequence expression supports being block-formatted,
/// like:
///
/// for (var e in [
/// element1,
/// element2,
/// ]) {
/// // ...
/// }
final bool _canBlockSplitSequence;
_ForInPiece3Dot7(
super._variable,
super._sequence, {
bool canBlockSplitSequence = false,
}) : _canBlockSplitSequence = canBlockSplitSequence,
super._();
@override
Set<Shape> allowedChildShapes(State state, Piece child) {
if (state == State.split) return Shape.all;
// Always allow block-splitting the sequence if it supports it.
return Shape.anyIf(child == _sequence && _canBlockSplitSequence);
}
}