Merge pull request #1033 from dart-lang/simpler-cascades
Simpler cascades
diff --git a/benchmark/after.dart.txt b/benchmark/after.dart.txt
index f72bd3d..8b8ec92 100644
--- a/benchmark/after.dart.txt
+++ b/benchmark/after.dart.txt
@@ -22,6 +22,96 @@
import 'version_queue.dart';
import 'version_solver.dart';
+class HttpRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ const $core.bool.fromEnvironment('protobuf.omit_message_names')
+ ? ''
+ : 'HttpRequest',
+ package: const $pb.PackageName(
+ const $core.bool.fromEnvironment('protobuf.omit_message_names')
+ ? ''
+ : 'google.logging.type'),
+ createEmptyInstance: create)
+ ..aOS(
+ 1,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'requestMethod')
+ ..aOS(
+ 2,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'requestUrl')
+ ..aInt64(
+ 3,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'requestSize')
+ ..a<$core.int>(
+ 4,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'status',
+ $pb.PbFieldType.O3)
+ ..aInt64(
+ 5,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'responseSize')
+ ..aOS(
+ 6,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'userAgent')
+ ..aOS(
+ 7,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'remoteIp')
+ ..aOS(
+ 8,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'referer')
+ ..aOB(
+ 9,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheHit')
+ ..aOB(
+ 10,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheValidatedWithOriginServer')
+ ..aOB(
+ 11,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheLookup')
+ ..aInt64(
+ 12,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheFillBytes')
+ ..aOS(
+ 13,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'serverIp')
+ ..aOM<$0.Duration>(
+ 14,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'latency',
+ subBuilder: $0.Duration.create)
+ ..aOS(
+ 15,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'protocol')
+ ..hasRequiredFields = false;
+}
+
/// The top-level solver.
///
/// Keeps track of the current potential solution, and the other possible
diff --git a/benchmark/before.dart.txt b/benchmark/before.dart.txt
index a542414..25f129a 100644
--- a/benchmark/before.dart.txt
+++ b/benchmark/before.dart.txt
@@ -22,6 +22,96 @@
import 'version_queue.dart';
import 'version_solver.dart';
+class HttpRequest extends $pb.GeneratedMessage {
+ static final $pb.BuilderInfo _i = $pb.BuilderInfo(
+ const $core.bool.fromEnvironment('protobuf.omit_message_names')
+ ? ''
+ : 'HttpRequest',
+ package: const $pb.PackageName(
+ const $core.bool.fromEnvironment('protobuf.omit_message_names')
+ ? ''
+ : 'google.logging.type'),
+ createEmptyInstance: create)
+ ..aOS(
+ 1,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'requestMethod')
+ ..aOS(
+ 2,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'requestUrl')
+ ..aInt64(
+ 3,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'requestSize')
+ ..a<$core.int>(
+ 4,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'status',
+ $pb.PbFieldType.O3)
+ ..aInt64(
+ 5,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'responseSize')
+ ..aOS(
+ 6,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'userAgent')
+ ..aOS(
+ 7,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'remoteIp')
+ ..aOS(
+ 8,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'referer')
+ ..aOB(
+ 9,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheHit')
+ ..aOB(
+ 10,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheValidatedWithOriginServer')
+ ..aOB(
+ 11,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheLookup')
+ ..aInt64(
+ 12,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'cacheFillBytes')
+ ..aOS(
+ 13,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'serverIp')
+ ..aOM<$0.Duration>(
+ 14,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'latency',
+ subBuilder: $0.Duration.create)
+ ..aOS(
+ 15,
+ const $core.bool.fromEnvironment('protobuf.omit_field_names')
+ ? ''
+ : 'protocol')
+ ..hasRequiredFields = false;
+}
+
/// The top-level solver.
///
/// Keeps track of the current potential solution, and the other possible
diff --git a/lib/src/call_chain_visitor.dart b/lib/src/call_chain_visitor.dart
index 4151cb7..fa4a779 100644
--- a/lib/src/call_chain_visitor.dart
+++ b/lib/src/call_chain_visitor.dart
@@ -173,14 +173,7 @@
this._blockCalls, this._hangingCall);
/// Builds chunks for the call chain.
- ///
- /// If [unnest] is `false` than this will not close the expression nesting
- /// created for the call chain and the caller must end it. Used by cascades
- /// to force a cascade after a method chain to be more deeply nested than
- /// the methods.
- void visit({bool? unnest}) {
- unnest ??= true;
-
+ void visit() {
_visitor.builder.nestExpression();
// Try to keep the entire method invocation one line.
@@ -259,8 +252,7 @@
_disableRule();
_endSpan();
-
- if (unnest) _visitor.builder.unnest();
+ _visitor.builder.unnest();
}
/// Returns `true` if the method chain should split if a split occurs inside
diff --git a/lib/src/chunk.dart b/lib/src/chunk.dart
index 7786d21..72681fa 100644
--- a/lib/src/chunk.dart
+++ b/lib/src/chunk.dart
@@ -225,9 +225,9 @@
}
/// Turns this chunk into one that can contain a block of child chunks.
- void makeBlock(Chunk? blockArgument) {
+ void makeBlock(Chunk? blockArgument, {required bool indent}) {
assert(_block == null);
- _block = ChunkBlock(blockArgument);
+ _block = ChunkBlock(blockArgument, indent);
}
/// Returns `true` if the block body owned by this chunk should be expression
@@ -290,10 +290,13 @@
/// may need extra expression-level indentation.
final Chunk? argument;
+ /// Whether the first chunk should have a level of indentation before it.
+ final bool indent;
+
/// The child chunks in this block.
final List<Chunk> chunks = [];
- ChunkBlock(this.argument);
+ ChunkBlock(this.argument, this.indent);
}
/// Constants for the cost heuristics used to determine which set of splits is
diff --git a/lib/src/chunk_builder.dart b/lib/src/chunk_builder.dart
index 71d955b..249250e 100644
--- a/lib/src/chunk_builder.dart
+++ b/lib/src/chunk_builder.dart
@@ -588,15 +588,15 @@
/// Starts a new block as a child of the current chunk.
///
- /// Nested blocks are handled using their own independent [LineWriter].
- ChunkBuilder startBlock(Chunk? argumentChunk) {
+ /// Nested blocks are handled using their own independent [LineWriter]. If
+ /// [indent] is `false` then the first line of the block will not get a level
+ /// of leading indentation. Otherwise it does.
+ ChunkBuilder startBlock(Chunk? argumentChunk, {bool indent = true}) {
var chunk = _chunks.last;
- chunk.makeBlock(argumentChunk);
+ chunk.makeBlock(argumentChunk, indent: indent);
var builder = ChunkBuilder._(this, _formatter, _source, chunk.block.chunks);
-
- // A block always starts off indented one level.
- builder.indent();
+ if (indent) builder.indent();
return builder;
}
diff --git a/lib/src/line_writer.dart b/lib/src/line_writer.dart
index e8874a8..13f883d 100644
--- a/lib/src/line_writer.dart
+++ b/lib/src/line_writer.dart
@@ -84,7 +84,8 @@
// TODO(rnystrom): Passing in an initial indent here is hacky. The
// LineWriter ensures all but the first chunk have a block indent, and this
// handles the first chunk. Do something cleaner.
- var result = writer.writeLines(Indent.block, flushLeft: chunk.flushLeft);
+ var result = writer.writeLines(chunk.block.indent ? Indent.block : 0,
+ flushLeft: chunk.flushLeft);
return _blockCache[key] = result;
}
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index a74e222..9b0517d 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -564,39 +564,27 @@
@override
void visitCascadeExpression(CascadeExpression node) {
+ // Optimized path if we know the cascade will split.
+ if (node.cascadeSections.length > 1) {
+ _visitSplitCascade(node);
+ return;
+ }
+
+ // Whether a split in the cascade target expression forces the cascade to
+ // move to the next line. It looks weird to move the cascade down if the
+ // target expression is a collection, so we don't:
+ //
+ // var list = [
+ // stuff
+ // ]
+ // ..add(more);
var splitIfOperandsSplit =
node.cascadeSections.length > 1 || _isCollectionLike(node.target);
-
- // If the cascade sections have consistent names they can be broken
- // normally otherwise they always get their own line.
if (splitIfOperandsSplit) {
builder.startLazyRule(_allowInlineCascade(node) ? Rule() : Rule.hard());
}
- // If the target of the cascade is a method call (or chain of them), we
- // treat the nesting specially. Normally, you would end up with:
- //
- // receiver
- // .method()
- // .method()
- // ..cascade()
- // ..cascade();
- //
- // This is logical, since the method chain is an operand of the cascade
- // expression, so it's more deeply nested. But it looks wrong, so we leave
- // the method chain's nesting active until after the cascade sections to
- // force the *cascades* to be deeper because it looks better:
- //
- // receiver
- // .method()
- // .method()
- // ..cascade()
- // ..cascade();
- if (node.target is MethodInvocation) {
- CallChainVisitor(this, node.target).visit(unnest: false);
- } else {
- visit(node.target);
- }
+ visit(node.target);
builder.nestExpression(indent: Indent.cascade, now: true);
builder.startBlockArgumentNesting();
@@ -621,8 +609,87 @@
builder.endBlockArgumentNesting();
builder.unnest();
+ }
- if (node.target is MethodInvocation) builder.unnest();
+ /// Format the cascade using a nested block instead of a single inline
+ /// expression.
+ ///
+ /// If the cascade has multiple sections, we know each section will be on its
+ /// own line and we know there will be at least one trailing section following
+ /// a preceding one. That let's us treat all of the earlier sections as a
+ /// separate block like we do with collections and functions, instead of a
+ /// monolithic expression. Using a block in turn makes big cascades much
+ /// faster to format (like 10x) since the block formatting is memoized and
+ /// each cascade section in it is formatted independently.
+ ///
+ /// The tricky part is that block formatting assumes the entire line will be
+ /// part of the block. This is not true of the last section in a cascade,
+ /// which may have other trailing code, like the `;` here:
+ ///
+ /// var x = someLeadingExpression
+ /// ..firstCascade()
+ /// ..secondCascade()
+ /// ..thirdCascade()
+ /// ..fourthCascade();
+ ///
+ /// To handle that, we don't put the last section in the block and instead
+ /// format it with the surrounding expression. So, from the formatter's
+ /// view, the above casade is formatted like:
+ ///
+ /// var x = someLeadingExpression
+ /// [ begin block ]
+ /// ..firstCascade()
+ /// ..secondCascade()
+ /// ..thirdCascade()
+ /// [ end block ]
+ /// ..fourthCascade();
+ ///
+ /// This somewhere between clever and hacky, but it works and allows cascades
+ /// of essentially unbounded length to be formatted quickly.
+ void _visitSplitCascade(CascadeExpression node) {
+ // Rule to split before the first `..`.
+ builder.startLazyRule(Rule.hard());
+ visit(node.target);
+
+ builder.nestExpression(indent: Indent.cascade, now: true);
+ builder.startBlockArgumentNesting();
+
+ zeroSplit();
+
+ // If there are comments before the first section, keep them outside of the
+ // block. That way code like:
+ //
+ // receiver // comment
+ // ..cascade();
+ //
+ // Keeps the comment on the first line.
+ var firstCommentToken = node.cascadeSections.first.beginToken;
+ writePrecedingCommentsAndNewlines(firstCommentToken);
+ _suppressPrecedingCommentsAndNewLines.add(firstCommentToken);
+
+ // Process the inner cascade sections as a separate block. This way the
+ // entire cascade expression isn't line split as a single monolithic unit,
+ // which is very slow.
+ builder = builder.startBlock(null, indent: false);
+
+ for (var i = 0; i < node.cascadeSections.length - 1; i++) {
+ if (i > 0) newline();
+ visit(node.cascadeSections[i]);
+ }
+
+ // Put comments before the last section inside the block.
+ var lastCommentToken = node.cascadeSections.last.beginToken;
+ writePrecedingCommentsAndNewlines(lastCommentToken);
+ _suppressPrecedingCommentsAndNewLines.add(lastCommentToken);
+
+ builder = builder.endBlock(null, forceSplit: true);
+
+ // The last section is outside of the block.
+ visit(node.cascadeSections.last);
+
+ builder.endRule();
+ builder.endBlockArgumentNesting();
+ builder.unnest();
}
/// Whether [expression] is a collection literal, or a call with a trailing
@@ -660,9 +727,13 @@
!hasCommaAfter(arguments.arguments.last);
}
- /// Whether a cascade should be allowed to be inline as opposed to one
- /// expression per line.
+ /// Whether a cascade should be allowed to be inline as opposed to moving the
+ /// section to the next line.
bool _allowInlineCascade(CascadeExpression node) {
+ // Cascades with multiple sections are handled elsewhere and are never
+ // inline.
+ assert(node.cascadeSections.length == 1);
+
// If the receiver is an expression that makes the cascade's very low
// precedence confusing, force it to split. For example:
//
@@ -674,23 +745,6 @@
if (node.target is PrefixExpression) return false;
if (node.target is AwaitExpression) return false;
- if (node.cascadeSections.length < 2) return true;
-
- var name;
- // We could be more forgiving about what constitutes sections with
- // consistent names but for now we require all sections to have the same
- // method name.
- for (var expression in node.cascadeSections) {
- if (expression is MethodInvocation) {
- if (name == null) {
- name = expression.methodName.name;
- } else if (name != expression.methodName.name) {
- return false;
- }
- } else {
- return false;
- }
- }
return true;
}
diff --git a/test/comments/cascades.stmt b/test/comments/cascades.stmt
new file mode 100644
index 0000000..ab01fe6
--- /dev/null
+++ b/test/comments/cascades.stmt
@@ -0,0 +1,67 @@
+>>> comments on single cascade lines
+receiver..cascade(); // comment
+<<<
+receiver..cascade(); // comment
+>>> comments on split cascade lines
+receiver
+ ..cascade() // a
+ ..cascade() // b
+ ..more(); // c
+<<<
+receiver
+ ..cascade() // a
+ ..cascade() // b
+ ..more(); // c
+>>> comment before first multi-line cascade section stays on line
+receiver // comment
+ ..cascade()
+ ..more();
+<<<
+receiver // comment
+ ..cascade()
+ ..more();
+>>> collapse blank lines before comment before first cascade
+receiver
+
+
+
+
+ // comment
+ ..cascade()
+ ..cascade();
+<<<
+receiver
+
+ // comment
+ ..cascade()
+ ..cascade();
+>>> preserve one blank line before comments on other cascades
+receiver
+
+
+
+
+ // comment
+ ..cascade()
+ // no blank
+ ..cascade()
+
+ // comment
+ ..cascade()
+
+
+ // comment
+ ..cascade();
+<<<
+receiver
+
+ // comment
+ ..cascade()
+ // no blank
+ ..cascade()
+
+ // comment
+ ..cascade()
+
+ // comment
+ ..cascade();
\ No newline at end of file
diff --git a/test/regression/0100/0137.stmt b/test/regression/0100/0137.stmt
index 159624f..d1c0906 100644
--- a/test/regression/0100/0137.stmt
+++ b/test/regression/0100/0137.stmt
@@ -16,11 +16,11 @@
lib.declarations.values
.where((d) => d is ClassMirror || d is MethodMirror)
.toList()
- ..sort((a, b) {
- if (a.runtimeType == b.runtimeType) {
- return _declarationName(a).compareTo(_declarationName(b));
- }
- if (a is MethodMirror && b is ClassMirror) return -1;
- if (a is ClassMirror && b is MethodMirror) return 1;
- return 0;
- });
\ No newline at end of file
+ ..sort((a, b) {
+ if (a.runtimeType == b.runtimeType) {
+ return _declarationName(a).compareTo(_declarationName(b));
+ }
+ if (a is MethodMirror && b is ClassMirror) return -1;
+ if (a is ClassMirror && b is MethodMirror) return 1;
+ return 0;
+ });
\ No newline at end of file
diff --git a/test/regression/0500/0591.unit b/test/regression/0500/0591.unit
index d91626e..f5112be 100644
--- a/test/regression/0500/0591.unit
+++ b/test/regression/0500/0591.unit
@@ -26,12 +26,11 @@
<<<
void fn80() {
// The number of characters in the `four` line is ..........................80
- var list = (one
- .two()
- .three()
+ var list =
+ (one.two().three()
..four(
'_15___20___25___30___35___40___45___50___55___60___65___70___75__'))
- ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
+ ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
;
}
>>>
@@ -45,12 +44,11 @@
<<<
void fn79() {
// The number of characters in the `four` line is .........................79
- var list = (one
- .two()
- .three()
+ var list =
+ (one.two().three()
..four(
'_15___20___25___30___35___40___45___50___55___60___65___70___75_'))
- ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
+ ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
;
}
>>>
@@ -64,12 +62,11 @@
<<<
void fn78() {
// The number of characters in the `four` line is ........................78
- var list = (one
- .two()
- .three()
+ var list =
+ (one.two().three()
..four(
'_15___20___25___30___35___40___45___50___55___60___65___70___75'))
- ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
+ ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
;
}
>>>
@@ -83,12 +80,11 @@
<<<
void fn77() {
// The number of characters in the `four` line is .......................77
- var list = (one
- .two()
- .three()
+ var list =
+ (one.two().three()
..four(
'_15___20___25___30___35___40___45___50___55___60___65___70___7'))
- ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
+ ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
;
}
>>>
@@ -102,10 +98,8 @@
<<<
void fn76() {
// The number of characters in the `four` line is ......................76
- var list = (one
- .two()
- .three()
- ..four('_15___20___25___30___35___40___45___50___55___60___65___70___'))
- ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
+ var list =
+ (one.two().three()..four('_15___20___25___30___35___40___45___50___55___60___65___70___'))
+ ..six('zzzzzzzzzzzzzzzzzzzzzzzzzzzz');
;
}
\ No newline at end of file
diff --git a/test/regression/0800/0881.unit b/test/regression/0800/0881.unit
new file mode 100644
index 0000000..f420d35
--- /dev/null
+++ b/test/regression/0800/0881.unit
@@ -0,0 +1,160 @@
+>>>
+void main() async {
+ group('my group', () {
+ setUp(() async {
+ final requestHandler = FakeRequestHandler()
+ ..when(withServiceName(Ng2ProtoFooBarBazService.serviceName))
+ .thenRespond(FooBarBazListResponse()
+ ..entities.add(FooBarBaz()
+ ..fooBarBazEntityId = entityId
+ ..name = 'Test entity'
+ ..baseFooBarBazId = baseFooBarBazId
+ ..entityFooBarBazId = entityFooBarBazId))
+ ..when(allOf(withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazId(baseFooBarBazId)))
+ .thenRespond(FooBarBazListResponse()..entities.add(baseFooBarBaz))
+ ..when(allOf(withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazId(entityFooBarBazId)))
+ .thenRespond(FooBarBazListResponse()..entities.add(entityFooBarBaz))
+ ..when(allOf(withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazIds(Set.from([baseFooBarBazId, entityFooBarBazId]))))
+ .thenRespond(
+ FooBarBazListResponse()..entities.addAll([baseFooBarBaz, entityFooBarBaz]));
+ });
+ });
+}
+<<<
+void main() async {
+ group('my group', () {
+ setUp(() async {
+ final requestHandler = FakeRequestHandler()
+ ..when(withServiceName(Ng2ProtoFooBarBazService.serviceName))
+ .thenRespond(FooBarBazListResponse()
+ ..entities.add(FooBarBaz()
+ ..fooBarBazEntityId = entityId
+ ..name = 'Test entity'
+ ..baseFooBarBazId = baseFooBarBazId
+ ..entityFooBarBazId = entityFooBarBazId))
+ ..when(allOf(withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazId(baseFooBarBazId)))
+ .thenRespond(FooBarBazListResponse()..entities.add(baseFooBarBaz))
+ ..when(allOf(withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazId(entityFooBarBazId)))
+ .thenRespond(FooBarBazListResponse()..entities.add(entityFooBarBaz))
+ ..when(allOf(
+ withServiceName(Ng2ProtoFooBarBazService.serviceName),
+ hasFooBarBazIds(
+ Set.from([baseFooBarBazId, entityFooBarBazId])))).thenRespond(
+ FooBarBazListResponse()
+ ..entities.addAll([baseFooBarBaz, entityFooBarBaz]));
+ });
+ });
+}
+>>>
+aaaa() {
+ {
+ {
+ {
+ aaaaa aaaaa = AaaAaaaa.aaaaaaa().aaaaaaa((a) => a
+ ..aaaaAaaaa
+ .aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa.AAA_AAAAAAAAAAA_AAAAA_AAAAAAA.aaaa] =
+ aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAAAA_AAAA_AAAAAAAAA.aaaa] = aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAAA_AAAA_AAAAAAAAA.aaaa] = aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAA_AAAAAAA_AAA_AAAAAAAAAAA_AAAAA.aaaa] = aaaa
+ ..aaaaaaaaAaaaaaAaaaa.aaaaaaa(AaaaaaaaAaaaaaAaaaaAaaaa.aaaaaaa())
+ ..aaaaaaaaAaaaa.aaaaaaaa =
+ (Aaaaaaaa()..aaaaaaaaAaaa = (AaaaaaaaAaaa()..aaAaaaaaa = aaaaa)));
+ }
+ }
+ }
+}
+<<<
+aaaa() {
+ {
+ {
+ {
+ aaaaa aaaaa = AaaAaaaa.aaaaaaa().aaaaaaa((a) => a
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[
+ AaaaAaaaaaaaaaAaaa_Aaaa.AAA_AAAAAAAAAAA_AAAAA_AAAAAAA.aaaa] = aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAAAA_AAAA_AAAAAAAAA.aaaa] = aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAAA_AAAA_AAAAAAAAA.aaaa] = aaaa
+ ..aaaaAaaaa.aaaaaaaaaaaAaAaaa[AaaaAaaaaaaaaaAaaa_Aaaa
+ .AAA_AAAAAAAAAAA_AAAAAA_AAAAAAA_AAA_AAAAAAAAAAA_AAAAA.aaaa] = aaaa
+ ..aaaaaaaaAaaaaaAaaaa.aaaaaaa(AaaaaaaaAaaaaaAaaaaAaaaa.aaaaaaa())
+ ..aaaaaaaaAaaaa.aaaaaaaa =
+ (Aaaaaaaa()..aaaaaaaaAaaa = (AaaaaaaaAaaa()..aaAaaaaaa = aaaaa)));
+ }
+ }
+ }
+}
+>>>
+void main() {
+ Example()
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(), ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(), ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(), ClassWithLongName(), ClassWithLongName(), ClassWithLongName());
+}
+<<<
+void main() {
+ Example()
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName())
+ ..methodWithLongName(ClassWithLongName(), ClassWithLongName(),
+ ClassWithLongName(), ClassWithLongName(), ClassWithLongName());
+}
\ No newline at end of file
diff --git a/test/regression/other/cascades.unit b/test/regression/other/cascades.unit
new file mode 100644
index 0000000..78c7eed
--- /dev/null
+++ b/test/regression/other/cascades.unit
@@ -0,0 +1,48 @@
+>>> over_react-4.1.0/tools/analyzer_plugin/playground/web/pseudo_static_lifecycle.dart
+class C {
+ get defaultProps {
+ return newProps() // This newProps() call should not lint
+ ..addProps(
+ super.defaultProps) // This super.defaultProps access should not lint
+ ..somethingThatCanBeTouched =
+ mcHammer; // This mcHammer access SHOULD lint
+ }
+}
+<<<
+class C {
+ get defaultProps {
+ return newProps() // This newProps() call should not lint
+ ..addProps(
+ super.defaultProps) // This super.defaultProps access should not lint
+ ..somethingThatCanBeTouched =
+ mcHammer; // This mcHammer access SHOULD lint
+ }
+}
+>>> sass-1.32.8/lib/src/executable/options.dart
+class C {
+ static final ArgParser _parser = () {
+ var parser = ArgParser(allowTrailingOptions: true)
+
+ // This is used for compatibility with sass-spec, even though we don't
+ // support setting the precision.
+ ..addOption('precision', hide: true)
+
+ // This is used when testing to ensure that the asynchronous evaluator path
+ // works the same as the synchronous one.
+ ..addFlag('async', hide: true);
+ };
+}
+<<<
+class C {
+ static final ArgParser _parser = () {
+ var parser = ArgParser(allowTrailingOptions: true)
+
+ // This is used for compatibility with sass-spec, even though we don't
+ // support setting the precision.
+ ..addOption('precision', hide: true)
+
+ // This is used when testing to ensure that the asynchronous evaluator path
+ // works the same as the synchronous one.
+ ..addFlag('async', hide: true);
+ };
+}
\ No newline at end of file
diff --git a/test/splitting/invocations.stmt b/test/splitting/invocations.stmt
index 6650f93..8ce34f1 100644
--- a/test/splitting/invocations.stmt
+++ b/test/splitting/invocations.stmt
@@ -131,7 +131,9 @@
>>> unsplit cascade unsplit method
object.method().method()..c()..c();
<<<
-object.method().method()..c()..c();
+object.method().method()
+ ..c()
+ ..c();
>>> split cascade unsplit method
object.method().method()..cascade()..cascade();
<<<
@@ -146,9 +148,9 @@
.method()
.method()
.method()
- ..cascade()
- ..cascade()
- ..cascade();
+ ..cascade()
+ ..cascade()
+ ..cascade();
>>> cascade setters on method chain
object.method().method().method().method()..x=1..y=2;
<<<
@@ -157,8 +159,8 @@
.method()
.method()
.method()
- ..x = 1
- ..y = 2;
+ ..x = 1
+ ..y = 2;
>>> cascade index
object..[index]..method()..[index]=value;
<<<
diff --git a/test/whitespace/cascades.stmt b/test/whitespace/cascades.stmt
index 03377d2..c34560d 100644
--- a/test/whitespace/cascades.stmt
+++ b/test/whitespace/cascades.stmt
@@ -14,7 +14,9 @@
..add("baz")
..add("bar");
<<<
-list..add("baz")..add("bar");
+list
+ ..add("baz")
+ ..add("bar");
>>> cascades indent contained blocks (and force multi-line) multiple cascades get their own line when method names are different
foo..fooBar()..toString();
<<<
@@ -148,7 +150,9 @@
?..add("baz")
..add("bar");
<<<
-list?..add("baz")..add("bar");
+list
+ ?..add("baz")
+ ..add("bar");
>>> mixed
foo?..a()..b()..c();
<<<
diff --git a/test/whitespace/functions.unit b/test/whitespace/functions.unit
index e17e716..a58d0ed 100644
--- a/test/whitespace/functions.unit
+++ b/test/whitespace/functions.unit
@@ -35,7 +35,9 @@
..add(1)
..add(2);
<<<
-fish() => []..add(1)..add(2);
+fish() => []
+ ..add(1)
+ ..add(2);
>>>
fish() => []..add(1);
<<<