blob: 3303b7a4e736346bffad0f299277ddd054e7a704 [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 '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
];
@override
void format(CodeWriter writer, State state) {
// There are constraints between how the parameters may split and now the
// initializers may split.
var (parameterState, initializerState) = switch (state) {
// If there are no initializers, the parameters can do whatever.
State.unsplit when _initializers == null => (null, null),
// All parameters and initializers on one line.
State.unsplit => (State.unsplit, State.unsplit),
// If the `:` splits, then the parameters can't.
_splitBeforeInitializers => (State.unsplit, State.split),
// The `) :` on its own line.
_splitBetweenInitializers => (State.split, State.split),
_ => throw ArgumentError(),
};
// 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.
if (_initializers != null && state == State.unsplit) {
writer.setAllowNewlines(false);
}
writer.format(_header);
writer.format(_parameters, requireState: parameterState);
if (_redirect case var redirect?) {
writer.space();
writer.format(redirect);
}
if (_initializers case var initializers?) {
writer.setAllowNewlines(state != State.unsplit);
writer.splitIf(state == _splitBeforeInitializers, indent: 2);
writer.format(_initializerSeparator!);
writer.space();
// Indent subsequent initializers past the `:`.
if (_hasOptionalParameter && state == _splitBetweenInitializers) {
// If the parameter list ends in `]) : init...` then we need to indent
// +5 to line up subsequent initializers.
writer.setIndent(5);
} else {
writer.setIndent(4);
}
writer.format(initializers, requireState: initializerState);
}
writer.setIndent(0);
writer.setAllowNewlines(true);
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';
}