| // 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 a series of binary expressions at the same precedence, like: |
| /// |
| /// a + b + c |
| abstract base class InfixPiece extends Piece { |
| /// The series of operands. |
| /// |
| /// Since we don't split on both sides of the operator, the operators will be |
| /// embedded in the operand pieces. If the operator is a hanging one, it will |
| /// be in the preceding operand, so `1 + 2` becomes "Infix(`1 +`, `2`)". |
| /// A leading operator like `foo as int` becomes "Infix(`foo`, `as int`)". |
| final List<Piece> _operands; |
| |
| /// What kind of indentation should be applied to the subsequent operands. |
| final Indent _indent; |
| |
| /// Creates an [InfixPiece] for the given series of [operands]. |
| factory InfixPiece( |
| List<Piece> operands, { |
| required bool is3Dot7, |
| bool conditional = false, |
| Indent indent = Indent.expression, |
| }) { |
| if (is3Dot7) { |
| return _InfixPiece3Dot7(operands, indent); |
| } else { |
| return _InfixPiece(operands, indent, conditional); |
| } |
| } |
| |
| /// Creates an [InfixPiece] for a conditional (`?:`) expression. |
| factory InfixPiece.conditional( |
| List<Piece> operands, { |
| required bool is3Dot7, |
| }) { |
| if (is3Dot7) { |
| return _InfixPiece3Dot7(operands, Indent.expression); |
| } else { |
| return _InfixPiece(operands, Indent.infix, true); |
| } |
| } |
| |
| InfixPiece._(this._operands, this._indent); |
| |
| @override |
| List<State> get additionalStates => const [State.split]; |
| |
| @override |
| Set<Shape> allowedChildShapes(State state, Piece child) => |
| Shape.anyIf(state == State.split); |
| |
| @override |
| void forEachChild(void Function(Piece piece) callback) { |
| _operands.forEach(callback); |
| } |
| |
| @override |
| State? fixedStateForPageWidth(int pageWidth) { |
| var totalLength = 0; |
| |
| for (var operand in _operands) { |
| // If any operand contains a newline, then we have to split. |
| if (operand.containsHardNewline) return State.split; |
| |
| totalLength += operand.totalCharacters; |
| if (totalLength > pageWidth) break; |
| } |
| |
| // If the total length doesn't fit in the page, then we have to split. |
| if (totalLength > pageWidth) return State.split; |
| |
| return null; |
| } |
| } |
| |
| /// InfixPiece subclass for 3.8 and newer style. |
| final class _InfixPiece extends InfixPiece { |
| /// Whether this piece is for a conditional expression. |
| final bool _isConditional; |
| |
| _InfixPiece(super.operands, super.indent, this._isConditional) : super._(); |
| |
| @override |
| void format(CodeWriter writer, State state) { |
| writer.pushIndent(_indent); |
| |
| // If this is a conditional expression (or chain of them), then allow the |
| // leading condition to be headline formatted in an assignment, like: |
| // |
| // variable = condition |
| // ? thenBranch |
| // : elseBranch; |
| // |
| // We only do this for conditional expressions and not other infix operators |
| // because with other operators, the operands are homogeneous and it makes |
| // more sense to split before the first one so that they are aligned in |
| // parallel: |
| // |
| // variable = |
| // operand + |
| // another; |
| if (_isConditional) writer.setShapeMode(ShapeMode.beforeHeadline); |
| writer.format(_operands[0]); |
| if (_isConditional) writer.setShapeMode(ShapeMode.afterHeadline); |
| |
| for (var i = 1; i < _operands.length; i++) { |
| writer.splitIf(state == State.split); |
| |
| // If this is a branch of a conditional expression, then indent the |
| // branch's contents past the `?` or `:`. |
| if (_isConditional) writer.pushIndent(Indent.block); |
| |
| // We can format each operand separately if the operand is on its own |
| // line. This happens when the operator is split and we aren't the first |
| // or last operand. |
| var separate = state == State.split && i < _operands.length - 1; |
| writer.format(_operands[i], separate: separate); |
| if (_isConditional) writer.popIndent(); |
| } |
| |
| writer.popIndent(); |
| } |
| } |
| |
| /// [InfixPiece] subclass for 3.7 style. |
| final class _InfixPiece3Dot7 extends InfixPiece { |
| _InfixPiece3Dot7(super.operands, super.indent) : super._(); |
| |
| @override |
| void format(CodeWriter writer, State state) { |
| writer.pushIndent(_indent); |
| |
| for (var i = 0; i < _operands.length; i++) { |
| // We can format each operand separately if the operand is on its own |
| // line. This happens when the operator is split and we aren't the first |
| // or last operand. |
| var separate = state == State.split && i > 0 && i < _operands.length - 1; |
| |
| writer.format(_operands[i], separate: separate); |
| if (i < _operands.length - 1) writer.splitIf(state == State.split); |
| } |
| |
| writer.popIndent(); |
| } |
| } |