blob: 546682c73e5d2d4b5cafce4464c8c776a5ea78a1 [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 piece for any construct where `=` is followed by an expression: variable
/// initializer, assignment, constructor initializer, etc.
///
/// This piece is also used for map entries and named arguments where `:` is
/// followed by an expression or element because those also want to support the
/// "block-like" formatting of delimited expressions on the right, and for the
/// `in` clause in for-in loops.
///
/// These constructs can be formatted four ways:
///
/// [State.unsplit] No split at all:
///
/// var x = 123;
///
/// This state also allows splitting the right side if it can be block
/// formatted:
///
/// var list = [
/// element,
/// ];
///
/// [_blockSplitLeft] Force the left-hand side, which must be a [ListPiece], to
/// split. Allow the right side to split or not. Allows all of:
///
/// var [
/// element,
/// ] = unsplitRhs;
///
/// var [
/// element,
/// ] = [
/// 'block split RHS',
/// ];
///
/// var [
/// element,
/// ] = 'expression split' +
/// 'the right hand side';
///
/// [_blockSplitRight] Allow the right-hand side to block split or not, if it
/// wants. Since [State.unsplit] and [_blockSplitLeft] also allow the
/// right-hand side to block split, this state is only used when the left-hand
/// side expression splits, like:
///
/// var (variable &&
/// anotherVariable) = [
/// element,
/// ];
///
/// [_atOperator] Split at the `=` or `in` operator and allow expression
/// splitting in either operand. Allows all of:
///
/// var (longVariable &&
/// anotherVariable) =
/// longOperand +
/// anotherOperand;
///
/// var [unsplitBlock] =
/// longOperand +
/// anotherOperand;
class AssignPiece extends Piece {
/// Force the block left-hand side to split and allow the right-hand side to
/// split.
static const State _blockSplitLeft = State(1);
/// Allow the right-hand side to block split.
static const State _blockSplitRight = State(2);
/// Split at the operator.
static const State _atOperator = State(3);
/// The left-hand side of the operation. Includes the operator unless it is
/// `in`.
final Piece? _left;
final Piece _operator;
/// The right-hand side of the operation.
final Piece _right;
final bool _splitBeforeOperator;
/// If `true`, then the left side supports being block-formatted, like:
///
/// var [
/// element1,
/// element2,
/// ] = value;
final bool _canBlockSplitLeft;
/// If `true` then the right side supports being block-formatted, like:
///
/// var list = [
/// element1,
/// element2,
/// ];
final bool _canBlockSplitRight;
AssignPiece(this._operator, this._right,
{Piece? left,
bool splitBeforeOperator = false,
bool canBlockSplitLeft = false,
bool canBlockSplitRight = false})
: _left = left,
_splitBeforeOperator = splitBeforeOperator,
_canBlockSplitLeft = canBlockSplitLeft,
_canBlockSplitRight = canBlockSplitRight;
// TODO(tall): The old formatter allows the first operand of a split
// conditional expression to be on the same line as the `=`, as in:
//
// var value = condition
// ? thenValue
// : elseValue;
//
// For now, we do not implement this special case behavior. Once more of the
// language is implemented in the new back end and we can run the formatter
// on a large corpus of code, we can try it out and see if the special case
// behavior is worth it.
@override
List<State> get additionalStates => [
// If at least one operand can block split, allow splitting in operands
// without splitting at the operator.
if (_canBlockSplitLeft) _blockSplitLeft,
if (_canBlockSplitRight) _blockSplitRight,
_atOperator,
];
/// Apply constraints between how the parameters may split and how the
/// initializers may split.
@override
void applyConstraints(State state, Constrain constrain) {
if (state == _blockSplitLeft) constrain(_left!, State.split);
}
@override
void format(CodeWriter writer, State state) {
switch (state) {
case State.unsplit:
_writeLeft(writer, allowNewlines: false);
_writeOperator(writer);
// Always allow block-splitting the right side if it supports it.
_writeRight(writer, allowNewlines: _canBlockSplitRight);
case _atOperator:
// When splitting at the operator, both operands may split or not and
// will be indented if they do.
writer.pushIndent(Indent.expression);
_writeLeft(writer);
_writeOperator(writer, split: state == _atOperator);
_writeRight(writer);
writer.popIndent();
case _blockSplitLeft:
_writeLeft(writer);
_writeOperator(writer);
_writeRight(writer, indent: !_canBlockSplitRight);
case _blockSplitRight:
_writeLeft(writer);
_writeOperator(writer, split: state == _atOperator);
_writeRight(writer);
}
}
void _writeLeft(CodeWriter writer, {bool allowNewlines = true}) {
if (_left case var left?) writer.format(left, allowNewlines: allowNewlines);
}
void _writeOperator(CodeWriter writer, {bool split = false}) {
if (_splitBeforeOperator) writer.splitIf(split);
writer.pushIndent(Indent.expression);
writer.format(_operator);
writer.popIndent();
if (!_splitBeforeOperator) writer.splitIf(split);
}
void _writeRight(CodeWriter writer,
{bool indent = false, bool allowNewlines = true}) {
if (indent) writer.pushIndent(Indent.expression);
writer.format(_right, allowNewlines: allowNewlines);
if (indent) writer.popIndent();
}
@override
void forEachChild(void Function(Piece piece) callback) {
if (_left case var left?) callback(left);
callback(_operator);
callback(_right);
}
}