blob: 58767f37c6e1bc8e6076fa9b9b373a217c5da2ed [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 constructor declaration.
///
/// This is somewhat similar to [FunctionPiece], but constructor initializers
/// add a lot of complexity. In particular, there are constraints between how
/// the parameter list is allowed to split and how the initializer list is
/// allowed to split. Only a few combinations of splits are allowed:
///
/// [State.unsplit] No splits at all, in the parameters or initializers.
///
/// SomeClass(param) : a = 1, b = 2;
///
/// [_splitBeforeInitializers] Split before the `:` and between the
/// initializers but not in the parameters.
///
/// SomeClass(param)
/// : a = 1,
/// b = 2;
///
/// [_splitBetweenInitializers] Split between the initializers but not before
/// the `:`. This state should only be chosen when the parameters split. If
/// there are no parameters, this state is excluded.
///
/// SomeClass(
/// param
/// ) : a = 1,
/// b = 2;
///
/// In addition, this piece deals with indenting initializers appropriately
/// based on whether the parameter list has a `]` or `}` before the `)`. If
/// there are optional parameters, then initializers after the first are
/// indented one space more to line up with the first initializer:
///
/// SomeClass(
/// mandatory,
/// ) : firstInitializer = 1,
/// second = 2;
/// // ^ Four spaces of indentation.
///
/// SomeClass([
/// optional,
/// ]) : firstInitializer = 1,
/// second = 2;
/// // ^ Five spaces of indentation.
class ConstructorPiece extends Piece {
static const _splitBeforeInitializers = State(1, cost: 1);
static const _splitBetweenInitializers = State(2, cost: 2);
/// True if there are parameters or comments inside the parameter list.
///
/// If so, then we allow splitting the parameter list while leaving the `:`
/// on the same line as the `)`.
final bool _canSplitParameters;
/// Whether the parameter list contains a `]` or `}` closing delimiter before
/// the `)`.
final bool _hasOptionalParameter;
/// The leading keywords, class name, and constructor name.
final Piece _header;
/// The constructor parameter list.
final Piece _parameters;
/// If this is a redirecting constructor, the redirection clause.
final Piece? _redirect;
/// If there are initializers, the `:` before them.
final Piece? _initializerSeparator;
/// The constructor initializers, if there are any.
final Piece? _initializers;
/// The constructor body.
final Piece _body;
ConstructorPiece(this._header, this._parameters, this._body,
{required bool canSplitParameters,
required bool hasOptionalParameter,
Piece? redirect,
Piece? initializerSeparator,
Piece? initializers})
: _canSplitParameters = canSplitParameters,
_hasOptionalParameter = hasOptionalParameter,
_redirect = redirect,
_initializerSeparator = initializerSeparator,
_initializers = initializers;
@override
List<State> get additionalStates => [
if (_initializers != null) _splitBeforeInitializers,
if (_canSplitParameters && _initializers != null)
_splitBetweenInitializers
];
/// Apply constraints between how the parameters may split and how the
/// initializers may split.
@override
void applyConstraints(State state, Constrain constrain) {
// If there are no initializers, the parameters can do whatever.
if (_initializers case var initializers?) {
switch (state) {
case State.unsplit:
// All parameters and initializers on one line.
constrain(_parameters, State.unsplit);
constrain(initializers, State.unsplit);
case _splitBeforeInitializers:
// Only split before the `:` when the parameters fit on one line.
constrain(_parameters, State.unsplit);
constrain(initializers, State.split);
case _splitBetweenInitializers:
// Split both the parameters and initializers and put the `) :` on
// its own line.
constrain(_parameters, State.split);
constrain(initializers, State.split);
}
}
}
@override
void format(CodeWriter writer, State state) {
// If there's a newline in the header or parameters (like a line comment
// after the `)`), then don't allow the initializers to remain unsplit.
var allowNewlines = _initializers == null || state != State.unsplit;
writer.format(_header, allowNewlines: allowNewlines);
writer.format(_parameters, allowNewlines: allowNewlines);
if (_redirect case var redirect?) {
writer.space();
writer.format(redirect, allowNewlines: allowNewlines);
}
if (_initializers case var initializers?) {
writer.pushIndent(Indent.block);
writer.splitIf(state == _splitBeforeInitializers);
writer.format(_initializerSeparator!, allowNewlines: allowNewlines);
writer.space();
// Indent subsequent initializers past the `:`.
if (_hasOptionalParameter && state == _splitBetweenInitializers) {
writer.pushIndent(Indent.initializerWithOptionalParameter);
} else {
writer.pushIndent(Indent.initializer);
}
writer.format(initializers, allowNewlines: allowNewlines);
writer.popIndent();
writer.popIndent();
}
writer.format(_body);
}
@override
void forEachChild(void Function(Piece piece) callback) {
callback(_header);
callback(_parameters);
if (_redirect case var redirect?) callback(redirect);
if (_initializerSeparator case var separator?) callback(separator);
if (_initializers case var initializers?) callback(initializers);
callback(_body);
}
@override
String get debugName => 'Ctor';
}