| // 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'; |
| } |