blob: 1ffe0eff170e349efa1d3dbf91e0c77ecca14990 [file] [log] [blame]
// Copyright (c) 2024, 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';
/// Piece for a case pattern, guard, and body in a switch expression.
class CaseExpressionPiece extends Piece {
/// Split after the `=>` before the body.
static const State _beforeBody = State(1);
/// Split before the `when` guard clause.
static const State _beforeWhen = State(2);
/// Split before the `when` guard clause and after the `=>`.
static const State _beforeWhenAndBody = State(3);
/// The pattern the value is matched against along with the leading `case`.
final Piece _pattern;
/// If there is a `when` clause, that clause.
final Piece? _guard;
/// The `=>` token separating the pattern and body.
final Piece _arrow;
/// The case body expression.
final Piece _body;
/// Whether the pattern can be block formatted.
final bool _canBlockSplitPattern;
/// Whether the outermost pattern is a logical-or pattern.
///
/// We format these specially to make them look like parallel cases:
///
/// switch (obj) {
/// firstPattern ||
/// secondPattern ||
/// thirdPattern =>
/// body;
/// }
final bool _patternIsLogicalOr;
/// Whether the body expression can be block formatted.
final bool _canBlockSplitBody;
CaseExpressionPiece(this._pattern, this._guard, this._arrow, this._body,
{required bool canBlockSplitPattern,
required bool patternIsLogicalOr,
required bool canBlockSplitBody})
: _canBlockSplitPattern = canBlockSplitPattern,
_patternIsLogicalOr = patternIsLogicalOr,
_canBlockSplitBody = canBlockSplitBody;
@override
List<State> get additionalStates => [
_beforeBody,
if (_guard != null) ...[_beforeWhen, _beforeWhenAndBody],
];
@override
void format(CodeWriter writer, State state) {
var allowNewlineInPattern = false;
var allowNewlineInGuard = false;
var allowNewlineInBody = false;
switch (state) {
case State.unsplit:
allowNewlineInBody = _canBlockSplitBody;
break;
case _beforeBody:
allowNewlineInPattern = _guard == null || _patternIsLogicalOr;
allowNewlineInBody = true;
case _beforeWhen:
// Allow newlines only in the pattern if we split before `when`.
allowNewlineInPattern = true;
case _beforeWhenAndBody:
allowNewlineInPattern = true;
allowNewlineInGuard = true;
allowNewlineInBody = true;
}
// If there is a split guard, then indent the pattern past it.
var indentPatternForGuard = !_canBlockSplitPattern &&
!_patternIsLogicalOr &&
(state == _beforeWhen || state == _beforeWhenAndBody);
if (indentPatternForGuard) writer.pushIndent(Indent.expression);
writer.pushAllowNewlines(allowNewlineInPattern);
writer.format(_pattern);
writer.popAllowNewlines();
if (indentPatternForGuard) writer.popIndent();
if (_guard case var guard?) {
writer.pushIndent(Indent.expression);
writer.pushAllowNewlines(allowNewlineInGuard);
writer.splitIf(state == _beforeWhen || state == _beforeWhenAndBody);
writer.format(guard);
writer.popAllowNewlines();
writer.popIndent();
}
writer.space();
writer.format(_arrow);
if (state != State.unsplit) writer.pushIndent(Indent.block);
writer.splitIf(state == _beforeBody || state == _beforeWhenAndBody);
writer.pushAllowNewlines(allowNewlineInBody);
writer.format(_body);
writer.popAllowNewlines();
if (state != State.unsplit) writer.popIndent();
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_pattern);
if (_guard case var guard?) callback(guard);
callback(_arrow);
callback(_body);
}
}
/// Piece for a case pattern and guard in a switch statement.
///
/// Unlike [CaseExpressionPiece], this doesn't include the case body, because
/// in a statement, the body is formatted as separate elements in the
/// surrounding sequence.
///
/// This just handles splitting between the pattern and guard.
///
/// [State.unsplit] No split before the guard:
///
/// case pattern when condition:
///
/// [State.split] Split before the `when`:
///
/// case someVeryLongPattern ||
/// anotherSubpattern
/// when longGuardCondition &&
/// anotherOperand:
class CaseStatementPiece extends Piece {
/// The pattern the value is matched against along with the leading `case`.
final Piece _pattern;
/// If there is a `when` clause, that clause.
final Piece? _guard;
CaseStatementPiece(this._pattern, this._guard);
@override
List<State> get additionalStates => [
if (_guard != null) State.split,
];
@override
void format(CodeWriter writer, State state) {
writer.pushAllowNewlines(_guard == null || state == State.split);
// If there is a guard, then indent the pattern past it.
if (_guard != null) writer.pushIndent(Indent.expression);
writer.format(_pattern);
if (_guard != null) writer.popIndent();
if (_guard case var guard?) {
writer.pushIndent(Indent.expression);
writer.splitIf(state == State.split);
writer.format(guard);
writer.popIndent();
}
writer.popAllowNewlines();
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_pattern);
if (_guard case var guard?) callback(guard);
}
}