| // 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 dotted series of property access or method calls, like: |
| /// |
| /// target.getter.method().another.method(); |
| /// |
| /// This piece handles splitting before the `.` and controlling which argument |
| /// lists in the method calls are allowed to contain newlines. |
| /// |
| /// Chains can split in four ways: |
| /// |
| /// [State.unsplit] The entire chain on one line: |
| /// |
| /// target.getter.method().another.method(); |
| /// |
| /// [_blockFormatTrailingCall] Don't split before any `.`. Split the last (or |
| /// next-to-last if there is a hanging unsplittable call at the end) method |
| /// call in the chain like a block while leaving other calls unsplit, as in: |
| /// |
| /// target.property.first(1).block( |
| /// argument, |
| /// argument, |
| /// ); |
| /// |
| /// [_splitAfterProperties] Split the call chain at each method call, but leave |
| /// the leading properties on the same line as the target. We allow leading |
| /// properties to remain unsplit while splitting the rest of the chain since |
| /// property accesses often feel "closer" to the target then the methods called |
| /// on it, as in: |
| /// |
| /// motorcycle.wheels.front |
| /// .rotate(); |
| /// |
| /// [State.split] Split before every `.` and indent the chain, like: |
| /// |
| /// target |
| /// .getter |
| /// .method( |
| /// argument, |
| /// argument, |
| /// ) |
| /// .another |
| /// .method( |
| /// argument, |
| /// argument, |
| /// ); |
| /// |
| /// A challenge with formatting call chains is how they look inside an |
| /// assignment, named argument, or `=>`. We don't a newline in a call chain to |
| /// force the surrounding assignment to split, because that's unnecessarily |
| /// spread out: |
| /// |
| /// variable = |
| /// target |
| /// .method() |
| /// .another(); |
| /// |
| /// It's better as: |
| /// |
| /// variable = target |
| /// .method() |
| /// .another(); |
| /// |
| /// But if the target itself splits, then we do want to force the assignment to |
| /// split: |
| /// |
| /// // Worse: |
| /// variable = (veryLongTarget + |
| /// anotherOperand) |
| /// .method() |
| /// .another(); |
| /// |
| /// // Better: |
| /// variable = |
| /// (veryLongTarget |
| /// anotherOperand) |
| /// .method() |
| /// .another(); |
| /// |
| /// The 3.7 formatter had a limited ability to handle code like the above. In |
| /// 3.8 and later, [Shape.headline] lets us express the constraint we want here |
| /// directly. Since the logic is different, there are two different [ChainPiece] |
| /// subclasses for each. |
| abstract base class ChainPiece extends Piece { |
| /// Allow newlines in the last (or next-to-last) call but nowhere else. |
| static const State _blockFormatTrailingCall = State(1, cost: 0); |
| |
| /// Split the call chain at each method call, but leave the leading properties |
| /// on the same line as the target. |
| static const State _splitAfterProperties = State(2); |
| |
| /// The target expression at the beginning of the call chain. |
| final Piece _target; |
| |
| /// The series of calls. |
| /// |
| /// The first piece in this is the target, and the rest are operations. |
| final List<ChainCall> _calls; |
| |
| /// The number of contiguous calls at the beginning of the chain that are |
| /// properties. |
| final int _leadingProperties; |
| |
| /// The index of the call in the chain that may be block formatted or `-1` if |
| /// none can. |
| /// |
| /// This will either be the index of the last call, or the index of the |
| /// second to last call if the last call is a property or unsplittable call |
| /// and the last call's argument list can be block formatted. |
| final int _blockCallIndex; |
| |
| /// How to indent the chain when it splits. |
| /// |
| /// This is [Indent.expression] for regular chains or [Indent.cascade] |
| /// for cascades. |
| final Indent _indent; |
| |
| final bool _isCascade; |
| |
| /// Creates a new ChainPiece. |
| /// |
| /// Instead of calling this directly, prefer using [ChainBuilder]. |
| factory ChainPiece( |
| Piece target, |
| List<ChainCall> calls, { |
| required bool cascade, |
| int leadingProperties = 0, |
| int blockCallIndex = -1, |
| Indent indent = Indent.expression, |
| required bool allowSplitInTarget, |
| required bool version37, |
| }) { |
| if (version37) { |
| return _ChainPieceV37( |
| target, |
| calls, |
| cascade: cascade, |
| leadingProperties: leadingProperties, |
| blockCallIndex: blockCallIndex, |
| allowSplitInTarget: allowSplitInTarget, |
| indent: indent, |
| ); |
| } else { |
| return _ChainPiece( |
| target, |
| calls, |
| cascade: cascade, |
| leadingProperties: leadingProperties, |
| blockCallIndex: blockCallIndex, |
| indent: indent, |
| ); |
| } |
| } |
| |
| /// Creates a new ChainPiece. |
| /// |
| /// Instead of calling this directly, prefer using [ChainBuilder]. |
| ChainPiece._( |
| this._target, |
| this._calls, { |
| required bool cascade, |
| int leadingProperties = 0, |
| int blockCallIndex = -1, |
| Indent indent = Indent.expression, |
| }) : _leadingProperties = leadingProperties, |
| _blockCallIndex = blockCallIndex, |
| _indent = indent, |
| _isCascade = cascade, |
| // If there are no calls, we shouldn't have created a chain. |
| assert(_calls.isNotEmpty); |
| |
| @override |
| List<State> get additionalStates => [ |
| if (_blockCallIndex != -1) _blockFormatTrailingCall, |
| if (_leadingProperties > 0) _splitAfterProperties, |
| State.split, |
| ]; |
| |
| @override |
| int stateCost(State state) { |
| // If the chain is a cascade, lower the cost so that we prefer splitting |
| // the cascades instead of the target. Prefers: |
| // |
| // [element1, element2] |
| // ..cascade(); |
| // |
| // Over: |
| // |
| // [ |
| // element1, |
| // element2, |
| // ]..cascade(); |
| if (state == State.split) return _isCascade ? 0 : 1; |
| |
| return super.stateCost(state); |
| } |
| |
| @override |
| void format(CodeWriter writer, State state) { |
| switch (state) { |
| case State.unsplit: |
| writer.format(_target); |
| |
| for (var i = 0; i < _calls.length; i++) { |
| writer.format(_calls[i]._call); |
| } |
| |
| case ChainPiece._splitAfterProperties: |
| writer.pushIndent(_indent); |
| writer.setShapeMode(ShapeMode.beforeHeadline); |
| writer.format(_target); |
| |
| for (var i = 0; i < _leadingProperties; i++) { |
| writer.format(_calls[i]._call); |
| } |
| |
| writer.setShapeMode(ShapeMode.afterHeadline); |
| |
| for (var i = _leadingProperties; i < _calls.length; i++) { |
| writer.newline(); |
| |
| // Every non-property call except the last will be on its own line. |
| writer.format(_calls[i]._call, separate: i < _calls.length - 1); |
| } |
| |
| writer.popIndent(); |
| |
| case ChainPiece._blockFormatTrailingCall: |
| // Don't treat a cascade as block-shaped in the surrounding context |
| // even if it block splits. Prefer: |
| // |
| // variable = target |
| // ..cascade(argument); |
| // |
| // Over: |
| // |
| // variable = target..cascade( |
| // argument, |
| // ); |
| // |
| // Note how the former makes it clearer that `variable` will be assigned |
| // the value `target` and that the cascade is a secondary side-effect. |
| if (_isCascade) writer.setShapeMode(ShapeMode.other); |
| |
| writer.format(_target); |
| |
| for (var i = 0; i < _calls.length; i++) { |
| writer.format(_calls[i]._call); |
| } |
| |
| case State.split: |
| writer.pushIndent(_indent); |
| writer.setShapeMode(ShapeMode.beforeHeadline); |
| writer.format(_target); |
| writer.setShapeMode(ShapeMode.afterHeadline); |
| |
| for (var i = 0; i < _calls.length; i++) { |
| writer.newline(); |
| |
| // The chain is fully split so every call except for the last is on |
| // its own line. |
| writer.format(_calls[i]._call, separate: i < _calls.length - 1); |
| } |
| |
| writer.popIndent(); |
| } |
| } |
| |
| @override |
| void forEachChild(void Function(Piece piece) callback) { |
| callback(_target); |
| |
| for (var call in _calls) { |
| callback(call._call); |
| } |
| } |
| } |
| |
| /// A [ChainPiece] subclass for 3.8 and later style. |
| final class _ChainPiece extends ChainPiece { |
| /// Creates a new ChainPiece. |
| /// |
| /// Instead of calling this directly, prefer using [ChainBuilder]. |
| _ChainPiece( |
| super.target, |
| super.calls, { |
| required super.cascade, |
| super.leadingProperties, |
| super.blockCallIndex, |
| super.indent, |
| }) : super._(); |
| |
| @override |
| int stateCost(State state) { |
| // If the chain is only properties, try to keep them together. Prefers: |
| // |
| // variable = |
| // target.property.another; |
| // |
| // Over: |
| // |
| // variable = target |
| // .property |
| // .another; |
| if (state == State.split && |
| !_isCascade && |
| _leadingProperties == _calls.length) { |
| return 2; |
| } |
| |
| return super.stateCost(state); |
| } |
| |
| @override |
| Set<Shape> allowedChildShapes(State state, Piece child) { |
| if (child == _target) { |
| return switch (state) { |
| // If the chain itself isn't fully split, only allow block splitting |
| // in the target. |
| State.unsplit || |
| ChainPiece._blockFormatTrailingCall || |
| ChainPiece._splitAfterProperties => const {Shape.inline, Shape.block}, |
| _ => Shape.all, |
| }; |
| } else { |
| switch (state) { |
| case State.unsplit: |
| return Shape.onlyInline; |
| |
| case ChainPiece._splitAfterProperties: |
| // Don't allow splitting inside the properties. |
| for (var i = 0; i < _leadingProperties; i++) { |
| if (_calls[i]._call == child) return Shape.onlyInline; |
| } |
| |
| case ChainPiece._blockFormatTrailingCall: |
| return Shape.anyIf(_calls[_blockCallIndex]._call == child); |
| } |
| |
| return Shape.all; |
| } |
| } |
| } |
| |
| /// A [ChainPiece] subclass for 3.7 style. |
| final class _ChainPieceV37 extends ChainPiece { |
| /// Whether the target expression may contain newlines when the chain is not |
| /// fully split. (It may always contain newlines when the chain splits.) |
| /// |
| /// This is true for most expressions but false for delimited ones to avoid |
| /// this weird output: |
| /// |
| /// function( |
| /// argument, |
| /// ) |
| /// .method(); |
| final bool _allowSplitInTarget; |
| |
| /// Creates a new ChainPiece. |
| /// |
| /// Instead of calling this directly, prefer using [ChainBuilder]. |
| _ChainPieceV37( |
| super.target, |
| super.calls, { |
| required super.cascade, |
| super.leadingProperties, |
| super.blockCallIndex, |
| super.indent, |
| required bool allowSplitInTarget, |
| }) : _allowSplitInTarget = allowSplitInTarget, |
| super._(); |
| |
| @override |
| Set<Shape> allowedChildShapes(State state, Piece child) { |
| switch (state) { |
| case _ when child == _target: |
| return Shape.anyIf(_allowSplitInTarget || state == State.split); |
| |
| case State.unsplit: |
| return Shape.onlyInline; |
| |
| case ChainPiece._splitAfterProperties: |
| for (var i = 0; i < _leadingProperties; i++) { |
| if (_calls[i]._call == child) return Shape.onlyInline; |
| } |
| |
| case ChainPiece._blockFormatTrailingCall: |
| return Shape.anyIf(_calls[_blockCallIndex]._call == child); |
| } |
| |
| return Shape.all; |
| } |
| } |
| |
| /// A method or getter call in a call chain, along with any postfix operations |
| /// applies to it. |
| final class ChainCall { |
| /// Piece for the call. |
| Piece _call; |
| |
| final CallType type; |
| |
| ChainCall(this._call, this.type); |
| |
| bool get canSplit => |
| type == CallType.splittableCall || type == CallType.blockFormatCall; |
| |
| /// Applies a postfix operation to this call. |
| /// |
| /// Invokes [createPostfix] with the current piece for the call. That |
| /// callback should return a new piece that contains [target] followed by the |
| /// postfix operation. |
| void wrapPostfix(Piece Function(Piece target) createPostfix) { |
| _call = createPostfix(_call); |
| } |
| } |
| |
| /// What kind of "call" a dotted expression in a call chain is. |
| enum CallType { |
| /// A property access, like `.foo`. |
| property, |
| |
| /// A method call with an empty argument list that can't split. |
| unsplittableCall, |
| |
| /// A method call with a non-empty argument list that can split but not |
| /// block format. |
| splittableCall, |
| |
| /// A method call with a non-empty argument list that can be block formatted. |
| blockFormatCall, |
| } |