Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(718)

Unified Diff: packages/dart_style/lib/src/call_chain_visitor.dart

Issue 1521693002: Roll Observatory deps (charted -> ^0.3.0) (Closed) Base URL: https://chromium.googlesource.com/external/github.com/dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « packages/dart_style/lib/src/argument_list_visitor.dart ('k') | packages/dart_style/lib/src/chunk.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/dart_style/lib/src/call_chain_visitor.dart
diff --git a/packages/dart_style/lib/src/call_chain_visitor.dart b/packages/dart_style/lib/src/call_chain_visitor.dart
index 36faccaab5a9289b25f471e7a1f5adcd0efa1832..7b7fb2690c2ddf9e4e02eac493026882cfe66249 100644
--- a/packages/dart_style/lib/src/call_chain_visitor.dart
+++ b/packages/dart_style/lib/src/call_chain_visitor.dart
@@ -8,6 +8,7 @@ import 'package:analyzer/analyzer.dart';
import 'argument_list_visitor.dart';
import 'rule/argument.dart';
+import 'rule/rule.dart';
import 'source_visitor.dart';
/// Helper class for [SourceVisitor] that handles visiting and writing a
@@ -32,12 +33,58 @@ class CallChainVisitor {
/// order that they appear in the source.
final List<Expression> _calls;
+ /// The method calls containing block function literals that break the method
+ /// chain and escape its indentation.
+ ///
+ /// receiver.a().b().c(() {
+ /// ;
+ /// }).d(() {
+ /// ;
+ /// }).e();
+ ///
+ /// Here, it will contain `c` and `d`.
+ ///
+ /// The block calls must be contiguous and must be a suffix of the list of
+ /// calls (except for the one allowed hanging call). Otherwise, none of them
+ /// are treated as block calls:
+ ///
+ /// receiver
+ /// .a()
+ /// .b(() {
+ /// ;
+ /// })
+ /// .c(() {
+ /// ;
+ /// })
+ /// .d()
+ /// .e();
+ final List<Expression> _blockCalls;
+
+ /// If there is one or more block calls and a single chained expression after
+ /// that, this will be that expression.
+ ///
+ /// receiver.a().b().c(() {
+ /// ;
+ /// }).d(() {
+ /// ;
+ /// }).e();
+ ///
+ /// We allow a single hanging call after the blocks because it will never
+ /// need to split before its `.` and this accommodates the common pattern of
+ /// a trailing `toList()` or `toSet()` after a series of higher-order methods
+ /// on an iterable.
+ final Expression _hangingCall;
+
/// Whether or not a [Rule] is currently active for the call chain.
bool _ruleEnabled = false;
/// Whether or not the span wrapping the call chain is currently active.
bool _spanEnded = false;
+ /// After the properties are visited (if there are any), this will be the
+ /// rule used to split between them.
+ PositionalRule _propertyRule;
+
/// Creates a new call chain visitor for [visitor] starting with [node].
///
/// The [node] is the outermost expression containing the chained "."
@@ -51,14 +98,25 @@ class CallChainVisitor {
flatten(expression) {
target = expression;
- if (expression is MethodInvocation && expression.target != null) {
- flatten(expression.target);
+ // Treat index expressions where the target is a valid call in a method
+ // chain as being part of the call. Handles cases like:
+ //
+ // receiver
+ // .property
+ // .property[0]
+ // .property
+ // .method()[1][2];
+ var call = expression;
+ while (call is IndexExpression) call = call.target;
+
+ if (call is MethodInvocation && call.target != null) {
+ flatten(call.target);
calls.add(expression);
- } else if (expression is PropertyAccess && expression.target != null) {
- flatten(expression.target);
+ } else if (call is PropertyAccess && call.target != null) {
+ flatten(call.target);
calls.add(expression);
- } else if (expression is PrefixedIdentifier) {
- flatten(expression.prefix);
+ } else if (call is PrefixedIdentifier) {
+ flatten(call.prefix);
calls.add(expression);
}
}
@@ -74,17 +132,60 @@ class CallChainVisitor {
// .length;
var properties = [];
if (target is SimpleIdentifier) {
- properties =
- calls.takeWhile((call) => call is! MethodInvocation).toList();
+ properties = calls.takeWhile((call) {
+ // Step into index expressions to see what the index is on.
+ while (call is IndexExpression) call = call.target;
+ return call is! MethodInvocation;
+ }).toList();
}
calls.removeRange(0, properties.length);
- return new CallChainVisitor._(visitor, target, properties, calls);
+ // Separate out the block calls, if there are any.
+ var blockCalls;
+ var hangingCall;
+
+ var inBlockCalls = false;
+ for (var call in calls) {
+ // See if this call is a method call whose arguments are block formatted.
+ var isBlockCall = false;
+ if (call is MethodInvocation) {
+ var args = new ArgumentListVisitor(visitor, call.argumentList);
+ isBlockCall = args.hasBlockArguments;
+ }
+
+ if (isBlockCall) {
+ inBlockCalls = true;
+ if (blockCalls == null) blockCalls = [];
+ blockCalls.add(call);
+ } else if (inBlockCalls) {
+ // We found a non-block call after a block call.
+ if (call == calls.last) {
+ // It's the one allowed hanging one, so it's OK.
+ hangingCall = call;
+ break;
+ }
+
+ // Don't allow any of the calls to be block formatted.
+ blockCalls = null;
+ break;
+ }
+ }
+
+ if (blockCalls != null) {
+ for (var blockCall in blockCalls) calls.remove(blockCall);
+ }
+
+ if (hangingCall != null) {
+ calls.remove(hangingCall);
+ }
+
+ return new CallChainVisitor._(
+ visitor, target, properties, calls, blockCalls, hangingCall);
}
- CallChainVisitor._(
- this._visitor, this._target, this._properties, this._calls);
+ CallChainVisitor._(this._visitor, this._target, this._properties, this._calls,
+ this._blockCalls, this._hangingCall);
/// Builds chunks for the call chain.
///
@@ -100,6 +201,19 @@ class CallChainVisitor {
// Try to keep the entire method invocation one line.
_visitor.builder.startSpan();
+ // If a split in the target expression forces the first `.` to split, then
+ // start the rule now so that it surrounds the target.
+ var splitOnTarget = _forcesSplit(_target);
+
+ if (splitOnTarget) {
+ if (_properties.length > 1) {
+ _propertyRule = new MultiplePositionalRule(null, 0, 0);
+ _visitor.builder.startLazyRule(_propertyRule);
+ } else if (_calls.isNotEmpty) {
+ _enableRule(lazy: true);
+ }
+ }
+
_visitor.visit(_target);
// Leading properties split like positional arguments: either not at all,
@@ -108,39 +222,138 @@ class CallChainVisitor {
_visitor.soloZeroSplit();
_writeCall(_properties.single);
} else if (_properties.length > 1) {
- var argRule = new MultiplePositionalRule(null, 0, 0);
- _visitor.builder.startRule(argRule);
+ if (!splitOnTarget) {
+ _propertyRule = new MultiplePositionalRule(null, 0, 0);
+ _visitor.builder.startRule(_propertyRule);
+ }
for (var property in _properties) {
- argRule.beforeArgument(_visitor.zeroSplit());
+ _propertyRule.beforeArgument(_visitor.zeroSplit());
_writeCall(property);
}
_visitor.builder.endRule();
}
- // The remaining chain of calls generally split atomically (either all or
- // none), except that block arguments may split a chain into two parts.
+ // Indent any block arguments in the chain that don't get special formatting
+ // below. Only do this if there is more than one argument to avoid spurious
+ // indentation in cases like:
+ //
+ // object.method(wrapper(() {
+ // body;
+ // });
+ // TODO(rnystrom): Come up with a less arbitrary way to express this?
+ if (_calls.length > 1) _visitor.builder.startBlockArgumentNesting();
+
+ // The chain of calls splits atomically (either all or none). Any block
+ // arguments inside them get indented to line up with the `.`.
for (var call in _calls) {
_enableRule();
_visitor.zeroSplit();
_writeCall(call);
}
+ if (_calls.length > 1) _visitor.builder.endBlockArgumentNesting();
+
+ // If there are block calls, end the chain and write those without any
+ // extra indentation.
+ if (_blockCalls != null) {
+ _enableRule();
+ _visitor.zeroSplit();
+ _disableRule();
+
+ for (var blockCall in _blockCalls) {
+ _writeBlockCall(blockCall);
+ }
+
+ // If there is a hanging call after the last block, write it without any
+ // split before the ".".
+ if (_hangingCall != null) {
+ _writeCall(_hangingCall);
+ }
+ }
+
_disableRule();
_endSpan();
if (unnest) _visitor.builder.unnest();
}
+ /// Returns `true` if the method chain should split if a split occurs inside
+ /// [expression].
+ ///
+ /// In most cases, splitting in a method chain's target forces the chain to
+ /// split too:
+ ///
+ /// receiver(very, long, argument,
+ /// list) // <-- Split here...
+ /// .method(); // ...forces split here.
+ ///
+ /// However, if the target is a collection or function literal (or an
+ /// argument list ending in one of those), we don't want to split:
+ ///
+ /// receiver(inner(() {
+ /// ;
+ /// }).method(); // <-- Unsplit.
+ bool _forcesSplit(Expression expression) {
+ // TODO(rnystrom): Other cases we may want to consider handling and
+ // recursing into:
+ // * ParenthesizedExpression.
+ // * The right operand in an infix operator call.
+ // * The body of a `=>` function.
+
+ // Don't split right after a collection literal.
+ if (expression is ListLiteral) return false;
+ if (expression is MapLiteral) return false;
+
+ // Don't split right after a non-empty curly-bodied function.
+ if (expression is FunctionExpression) {
+ if (expression.body is! BlockFunctionBody) return false;
+
+ return (expression.body as BlockFunctionBody).block.statements.isEmpty;
+ }
+
+ // If the expression ends in an argument list, base the splitting on the
+ // last argument.
+ var argumentList;
+ if (expression is MethodInvocation) {
+ argumentList = expression.argumentList;
+ } else if (expression is InstanceCreationExpression) {
+ argumentList = expression.argumentList;
+ } else if (expression is FunctionExpressionInvocation) {
+ argumentList = expression.argumentList;
+ }
+
+ // Any other kind of expression always splits.
+ if (argumentList == null) return true;
+ if (argumentList.arguments.isEmpty) return true;
+
+ var argument = argumentList.arguments.last;
+ if (argument is NamedExpression) argument = argument.expression;
+
+ // TODO(rnystrom): This logic is similar (but not identical) to
+ // ArgumentListVisitor.hasBlockArguments. They overlap conceptually and
+ // both have their own peculiar heuristics. It would be good to unify and
+ // rationalize them.
+
+ return _forcesSplit(argument);
+ }
+
/// Writes [call], which must be one of the supported expression types.
void _writeCall(Expression call) {
- if (call is MethodInvocation) {
+ if (call is IndexExpression) {
+ _visitor.builder.nestExpression();
+ _writeCall(call.target);
+ _visitor.finishIndexExpression(call);
+ _visitor.builder.unnest();
+ } else if (call is MethodInvocation) {
_writeInvocation(call);
} else if (call is PropertyAccess) {
- _writePropertyAccess(call);
+ _visitor.token(call.operator);
+ _visitor.visit(call.propertyName);
} else if (call is PrefixedIdentifier) {
- _writePrefixedIdentifier(call);
+ _visitor.token(call.period);
+ _visitor.visit(call.identifier);
} else {
// Unexpected type.
assert(false);
@@ -151,38 +364,15 @@ class CallChainVisitor {
_visitor.token(invocation.operator);
_visitor.token(invocation.methodName.token);
- // If a method's argument list includes any block arguments, there's a
- // good chance it will split. Treat the chains before and after that as
- // separate unrelated method chains.
- //
- // This is kind of a hack since it treats methods before and after a
- // collection literal argument differently even when the collection
- // doesn't split, but it works out OK in practice.
- //
- // Doing something more precise would require setting up a bunch of complex
- // constraints between various rules. You'd basically have to say "if the
- // block argument splits then allow the chain after it to split
- // independently, otherwise force it to follow the previous chain".
- var args = new ArgumentListVisitor(_visitor, invocation.argumentList);
-
- // Stop the rule after the last call, but before its arguments. This
- // allows unsplit chains where the last argument list wraps, like:
+ // If we don't have any block calls, stop the rule after the last method
+ // call name, but before its arguments. This allows unsplit chains where
+ // the last argument list wraps, like:
//
// foo().bar().baz(
// argument, list);
- //
- // Also stop the rule to split the argument list at any call with
- // block arguments. This makes for nicer chains of higher-order method
- // calls, like:
- //
- // items.map((element) {
- // ...
- // }).where((element) {
- // ...
- // });
- if (invocation == _calls.last || args.hasBlockArguments) _disableRule();
-
- if (args.nestMethodArguments) _visitor.builder.startBlockArgumentNesting();
+ if (_blockCalls == null && _calls.isNotEmpty && invocation == _calls.last) {
+ _disableRule();
+ }
// For a single method call on an identifier, stop the span before the
// arguments to make it easier to keep the call name with the target. In
@@ -202,23 +392,18 @@ class CallChainVisitor {
// there looks really odd.
if (_properties.isEmpty &&
_calls.length == 1 &&
+ _blockCalls == null &&
_target is SimpleIdentifier) {
_endSpan();
}
_visitor.visit(invocation.argumentList);
-
- if (args.nestMethodArguments) _visitor.builder.endBlockArgumentNesting();
- }
-
- void _writePropertyAccess(PropertyAccess property) {
- _visitor.token(property.operator);
- _visitor.visit(property.propertyName);
}
- void _writePrefixedIdentifier(PrefixedIdentifier prefix) {
- _visitor.token(prefix.period);
- _visitor.visit(prefix.identifier);
+ void _writeBlockCall(MethodInvocation invocation) {
+ _visitor.token(invocation.operator);
+ _visitor.token(invocation.methodName.token);
+ _visitor.visit(invocation.argumentList);
}
/// If a [Rule] for the method chain is currently active, ends it.
@@ -230,10 +415,19 @@ class CallChainVisitor {
}
/// Creates a new method chain [Rule] if one is not already active.
- void _enableRule() {
+ void _enableRule({bool lazy: false}) {
if (_ruleEnabled) return;
- _visitor.builder.startRule();
+ // If the properties split, force the calls to split too.
+ var rule = new Rule();
+ if (_propertyRule != null) _propertyRule.setNamedArgsRule(rule);
+
+ if (lazy) {
+ _visitor.builder.startLazyRule(rule);
+ } else {
+ _visitor.builder.startRule(rule);
+ }
+
_ruleEnabled = true;
}
« no previous file with comments | « packages/dart_style/lib/src/argument_list_visitor.dart ('k') | packages/dart_style/lib/src/chunk.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698