blob: 823bb6103d33ac4292ec7e74485c52efc1374e98 [file] [log] [blame] [edit]
// 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 an assignment-like construct:
///
/// - Assignment (`=`, `+=`, etc.)
/// - Named arguments (`:`)
/// - Map entries (`:`)
/// - Record fields (`:`)
/// - Expression function bodies (`=>`)
///
/// Unlike other infix operators, these have some special formatting:
///
/// [State.unsplit] No split at all:
///
/// var x = 123;
///
/// This state also allows splitting the right side if block shaped:
///
/// 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';
///
/// [_blockOrHeadlineSplitRight] Require the right-hand side to be block or
/// headline shaped and allow the left-side to expression split as in:
///
/// var (variable &&
/// anotherVariable) = [
/// element,
/// ];
///
/// [State.split] 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;
final class AssignPiece extends Piece {
/// Allow the right-hand side to block split.
static const State _blockOrHeadlineSplitRight = State(1, cost: 0);
/// Force the left-hand side to block split and allow the right-hand side to
/// split.
static const State _blockSplitLeft = State(2);
/// The left-hand side of the operation and the operator itself.
final Piece _left;
/// The right-hand side of the operation.
final Piece _right;
/// Whether the piece should have a cost for splitting at the operator.
///
/// Usually true because it's generally better to block split inside the
/// operands when possible. But false for `=>` when the expression has a form
/// where we'd rather keep the expression itself unsplit as in:
///
/// // Don't avoid split:
/// makeStuff() => [
/// element,
/// element,
/// ];
///
/// // Avoid split:
/// doThing() =>
/// thingToDo(argument, argument);
final bool _avoidSplit;
AssignPiece(this._left, this._right, {bool avoidSplit = true})
: _avoidSplit = avoidSplit;
@override
List<State> get additionalStates => [
_blockOrHeadlineSplitRight,
_blockSplitLeft,
State.split,
];
@override
int stateCost(State state) => switch (state) {
State.split => _avoidSplit ? 1 : 0,
_ => super.stateCost(state),
};
@override
Set<Shape> allowedChildShapes(State state, Piece child) {
return switch (state) {
State.unsplit => Shape.onlyInline,
_blockSplitLeft when child == _left => Shape.onlyBlock,
_blockSplitLeft when child == _right => const {Shape.inline, Shape.other},
_blockOrHeadlineSplitRight when child == _right => const {
Shape.block,
Shape.headline,
},
_ => Shape.all,
};
}
@override
void format(CodeWriter writer, State state) {
if (state == State.split) {
// When splitting at the operator, indent the operands.
writer.pushIndent(Indent.expression);
// Treat a split `=` as potentially headline-shaped if the LHS doesn't
// split. Allows:
//
// variable = another =
// 'split at second "="';
writer.setShapeMode(ShapeMode.beforeHeadline);
writer.format(_left);
writer.setShapeMode(ShapeMode.afterHeadline);
writer.newline();
writer.popIndent();
writer.pushIndent(Indent.assignment);
writer.format(_right);
writer.popIndent();
} else {
writer.format(_left);
writer.space();
writer.format(_right);
}
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_left);
callback(_right);
}
}