Tweak the rules for when a cascade splits a little more. (#603)
The previous change was a little too aggressize and turned code like:
[1, 2, 3, 4, 5, 6]
..addAll([7, 8, 9]);
Into:
[
1,
2,
3,
4,
5,
6
]..addAll([7, 8, 9]);
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 7ce4e9b..0557d3d 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -340,9 +340,15 @@
}
visitCascadeExpression(CascadeExpression node) {
+ 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.
- builder.startLazyRule(_allowInlineCascade(node) ? new Rule() : new Rule.hard());
+ if (splitIfOperandsSplit) {
+ builder.startLazyRule(
+ _allowInlineCascade(node) ? new Rule() : new 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:
@@ -372,45 +378,52 @@
builder.nestExpression(indent: Indent.cascade, now: true);
builder.startBlockArgumentNesting();
- if (node.cascadeSections.length > 1 || _splitBeforeOnlyCascade(node.target)) {
- zeroSplit();
+ // If the cascade section shouldn't cause the cascade to split, end the
+ // rule early so it isn't affected by it.
+ if (!splitIfOperandsSplit) {
+ builder
+ .startRule(_allowInlineCascade(node) ? new Rule() : new Rule.hard());
}
+
+ zeroSplit();
+
+ if (!splitIfOperandsSplit) {
+ builder.endRule();
+ }
+
visitNodes(node.cascadeSections, between: zeroSplit);
- builder.endRule();
+ if (splitIfOperandsSplit) {
+ builder.endRule();
+ }
+
builder.endBlockArgumentNesting();
builder.unnest();
if (node.target is MethodInvocation) builder.unnest();
}
- /// Whether the split before the ".." in a single-section cascade should be
- /// included.
+ /// Whether [expression] is a collection literal, or a call with a trailing
+ /// comma in an argument list.
///
- /// We omit these when the target is a collection literal because it looks
- /// a little funny to have the cascade hanging on its own line, especially
- /// in the common "addAll()" pattern:
+ /// In that case, when the expression is a target of a cascade, we don't
+ /// force a split before the ".." as eagerly to avoid ugly results like:
///
/// [
/// 1,
/// 2,
/// ]..addAll(numbers);
- bool _splitBeforeOnlyCascade(Expression target) {
- // TODO(rnystrom): Consider still emitting a soft split but not starting
- // the rule until after the target to allow code like:
- //
- // ["this does", "not split"]
- // ..splitHere();
- if (target is ListLiteral) return false;
- if (target is MapLiteral) return false;
+ bool _isCollectionLike(Expression expression) {
+ if (expression is ListLiteral) return false;
+ if (expression is MapLiteral) return false;
// If the target is a call with a trailing comma in the argument list,
// treat it like a collection literal.
ArgumentList arguments;
- if (target is InvocationExpression) {
- arguments = target.argumentList;
- } else if (target is InstanceCreationExpression) {
- arguments = target.argumentList;
+ if (expression is InvocationExpression) {
+ arguments = expression.argumentList;
+ } else if (expression is InstanceCreationExpression) {
+ arguments = expression.argumentList;
}
// TODO(rnystrom): Do we want to allow an invocation where the last
@@ -1796,7 +1809,6 @@
visitNodes(metadata, between: newline, after: newline);
}
-
/// Visit metadata annotations for a directive.
///
/// Always force the annotations to be on a previous line.
@@ -1804,9 +1816,11 @@
// Preserve a blank line before the first directive since users (in
// particular the test package) sometimes use that for metadata that
// applies to the entire library and not the following directive itself.
- var isFirst = directive == (directive.parent as CompilationUnit).directives.first;
+ var isFirst =
+ directive == (directive.parent as CompilationUnit).directives.first;
- visitNodes(directive.metadata, between: newline, after: isFirst ? oneOrTwoNewlines : newline);
+ visitNodes(directive.metadata,
+ between: newline, after: isFirst ? oneOrTwoNewlines : newline);
}
/// Visits metadata annotations on parameters and type parameters.
diff --git a/test/whitespace/cascades.stmt b/test/whitespace/cascades.stmt
index 32cda4c..4fc7329 100644
--- a/test/whitespace/cascades.stmt
+++ b/test/whitespace/cascades.stmt
@@ -123,4 +123,23 @@
<<<
(foo)(
1,
-)..addAll(more);
\ No newline at end of file
+)..addAll(more);
+>>> allow keeping collection on one line and splitting cascade
+[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]..cascade();
+<<<
+[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+ ..cascade();
+>>> don't force cascade to split on collection target if arg splits
+[1, 2, 3, 4]..cascade(() {;});
+<<<
+[1, 2, 3, 4]..cascade(() {
+ ;
+ });
+>>> don't force cascade to split on collection target if target and arg split
+[1,]..cascade(() {;});
+<<<
+[
+ 1,
+]..cascade(() {
+ ;
+ });
\ No newline at end of file