| Index: lib/src/argument_list_visitor.dart
|
| diff --git a/lib/src/argument_list_visitor.dart b/lib/src/argument_list_visitor.dart
|
| index 61e820b621ff1f387b43add6157d1d613760ae84..4acb3b450cea00bff44c82b294a422e1a7ee18cf 100644
|
| --- a/lib/src/argument_list_visitor.dart
|
| +++ b/lib/src/argument_list_visitor.dart
|
| @@ -12,58 +12,40 @@ import 'rule/rule.dart';
|
| import 'source_visitor.dart';
|
|
|
| /// Helper class for [SourceVisitor] that handles visiting and writing an
|
| -/// [ArgumentList], including all of the special code needed to handle block
|
| -/// arguments.
|
| +/// [ArgumentList], including all of the special code needed to handle function
|
| +/// and collection arguments.
|
| class ArgumentListVisitor {
|
| final SourceVisitor _visitor;
|
|
|
| final ArgumentList _node;
|
|
|
| - /// The positional arguments, in order.
|
| - final List<Expression> _positional;
|
| -
|
| - /// The named arguments, in order.
|
| - final List<Expression> _named;
|
| -
|
| - /// The set of arguments that are valid block literals.
|
| - final Set<Expression> _blockArguments;
|
| + /// The normal arguments preceding any block function arguments.
|
| + final ArgumentSublist _arguments;
|
|
|
| - /// The number of leading block arguments.
|
| + /// The contiguous list of block function arguments, if any.
|
| ///
|
| - /// If all arguments are block arguments, this counts them.
|
| - final int _leadingBlockArguments;
|
| + /// Otherwise, this is `null`.
|
| + final List<Expression> _functions;
|
|
|
| - /// The number of trailing block arguments.
|
| + /// If there are block function arguments, this is the arguments after them.
|
| ///
|
| - /// If all arguments are block arguments, this is zero.
|
| - final int _trailingBlockArguments;
|
| -
|
| - /// The rule used to split the bodies of all of the block arguments.
|
| - Rule get _blockArgumentRule {
|
| - // Lazy initialize.
|
| - if (_blockRule == null && _blockArguments.isNotEmpty) {
|
| - _blockRule = new SimpleRule(cost: Cost.splitBlocks);
|
| - }
|
| -
|
| - return _blockRule;
|
| - }
|
| -
|
| - Rule _blockRule;
|
| + /// Otherwise, this is `null`.
|
| + final ArgumentSublist _argumentsAfterFunctions;
|
|
|
| /// Returns `true` if there is only a single positional argument.
|
| - bool get _isSingle => _positional.length == 1 && _named.isEmpty;
|
| + bool get _isSingle =>
|
| + _node.arguments.length == 1 && _node.arguments.single is! NamedExpression;
|
|
|
| - /// Whether this argument list has any block arguments that are functions.
|
| - bool get hasFunctionBlockArguments => _blockArguments.any(_isBlockFunction);
|
| -
|
| - bool get hasBlockArguments => _blockArguments.isNotEmpty;
|
| + /// Whether this argument list has any collection or block function arguments.
|
| + bool get hasBlockArguments =>
|
| + _arguments._collections.isNotEmpty || _functions != null;
|
|
|
| /// Whether this argument list should force the containing method chain to
|
| /// add a level of block nesting.
|
| bool get nestMethodArguments {
|
| // If there are block arguments, we don't want the method to force them to
|
| // the right.
|
| - if (_blockArguments.isNotEmpty) return false;
|
| + if (hasBlockArguments) return false;
|
|
|
| // Corner case: If there is just a single argument, don't bump the nesting.
|
| // This lets us avoid spurious indentation in cases like:
|
| @@ -75,61 +57,52 @@ class ArgumentListVisitor {
|
| }
|
|
|
| factory ArgumentListVisitor(SourceVisitor visitor, ArgumentList node) {
|
| - // Assumes named arguments follow all positional ones.
|
| - var positional =
|
| - node.arguments.takeWhile((arg) => arg is! NamedExpression).toList();
|
| - var named = node.arguments.skip(positional.length).toList();
|
| -
|
| - var blocks = node.arguments.where(_isBlockArgument).toSet();
|
| -
|
| - // Count the leading arguments that are block literals.
|
| - var leadingBlocks = 0;
|
| - for (var argument in node.arguments) {
|
| - if (!blocks.contains(argument)) break;
|
| - leadingBlocks++;
|
| - }
|
| -
|
| - // Count the trailing arguments that are block literals.
|
| - var trailingBlocks = 0;
|
| - if (leadingBlocks != node.arguments.length) {
|
| - for (var argument in node.arguments.reversed) {
|
| - if (!blocks.contains(argument)) break;
|
| - trailingBlocks++;
|
| + // Look for a single contiguous range of block function arguments.
|
| + var functionsStart;
|
| + var functionsEnd;
|
| +
|
| + for (var i = 0; i < node.arguments.length; i++) {
|
| + var argument = node.arguments[i];
|
| + if (_isBlockFunction(argument)) {
|
| + if (functionsStart == null) functionsStart = i;
|
| +
|
| + // The functions must be one contiguous section.
|
| + if (functionsEnd != null && functionsEnd != i) {
|
| + functionsStart = null;
|
| + functionsEnd = null;
|
| + break;
|
| + }
|
| +
|
| + functionsEnd = i + 1;
|
| }
|
| }
|
|
|
| - // If only some of the named arguments are blocks, treat none of them as
|
| - // blocks. Avoids cases like:
|
| - //
|
| - // function(
|
| - // a: arg,
|
| - // b: [
|
| - // ...
|
| - // ]);
|
| - if (trailingBlocks < named.length) trailingBlocks = 0;
|
| -
|
| - // Blocks must all be a prefix or suffix of the argument list (and not
|
| - // both).
|
| - if (leadingBlocks != blocks.length) leadingBlocks = 0;
|
| - if (trailingBlocks != blocks.length) trailingBlocks = 0;
|
| -
|
| - // Ignore any blocks in the middle of the argument list.
|
| - if (leadingBlocks == 0 && trailingBlocks == 0) {
|
| - blocks.clear();
|
| + if (functionsStart == null) {
|
| + // No functions, so there is just a single argument list.
|
| + return new ArgumentListVisitor._(visitor, node,
|
| + new ArgumentSublist(node.arguments, node.arguments), null, null);
|
| }
|
|
|
| - return new ArgumentListVisitor._(visitor, node, positional, named, blocks,
|
| - leadingBlocks, trailingBlocks);
|
| + // Split the arguments into two independent argument lists with the
|
| + // functions in the middle.
|
| + var argumentsBefore = node.arguments.take(functionsStart).toList();
|
| + var functions = node.arguments.sublist(functionsStart, functionsEnd);
|
| + var argumentsAfter = node.arguments.skip(functionsEnd).toList();
|
| +
|
| + return new ArgumentListVisitor._(
|
| + visitor,
|
| + node,
|
| + new ArgumentSublist(node.arguments, argumentsBefore),
|
| + functions,
|
| + new ArgumentSublist(node.arguments, argumentsAfter));
|
| }
|
|
|
| ArgumentListVisitor._(
|
| this._visitor,
|
| this._node,
|
| - this._positional,
|
| - this._named,
|
| - this._blockArguments,
|
| - this._leadingBlockArguments,
|
| - this._trailingBlockArguments);
|
| + this._arguments,
|
| + this._functions,
|
| + this._argumentsAfterFunctions);
|
|
|
| /// Builds chunks for the call chain.
|
| void visit() {
|
| @@ -143,65 +116,216 @@ class ArgumentListVisitor {
|
| _visitor.builder.startSpan();
|
| _visitor.token(_node.leftParenthesis);
|
|
|
| - var rule = _writePositional();
|
| - _writeNamed(rule);
|
| + _arguments.visit(_visitor);
|
| +
|
| + _visitor.builder.endSpan();
|
| +
|
| + if (_functions != null) {
|
| + // TODO(rnystrom): It might look better to treat the parameter list of the
|
| + // first function as if it were an argument in the preceding argument list
|
| + // instead of just having this little solo split here. That would try to
|
| + // keep the parameter list with other arguments when possible, and, I
|
| + // think, generally look nicer.
|
| + if (_functions.first == _node.arguments.first) {
|
| + _visitor.soloZeroSplit();
|
| + } else {
|
| + _visitor.soloSplit();
|
| + }
|
| +
|
| + for (var argument in _functions) {
|
| + if (argument != _functions.first) _visitor.space();
|
| +
|
| + _visitor.visit(argument);
|
| +
|
| + // Write the trailing comma.
|
| + if (argument != _node.arguments.last) {
|
| + _visitor.token(argument.endToken.next);
|
| + }
|
| + }
|
| +
|
| + _visitor.builder.startSpan();
|
| + _argumentsAfterFunctions.visit(_visitor);
|
| + _visitor.builder.endSpan();
|
| + }
|
|
|
| _visitor.token(_node.rightParenthesis);
|
|
|
| - _visitor.builder.endSpan();
|
| _visitor.builder.unnest();
|
|
|
| if (_isSingle) _visitor.builder.endSpan();
|
| }
|
|
|
| + /// Returns `true` if [expression] is a [FunctionExpression] with a block
|
| + /// body.
|
| + static bool _isBlockFunction(Expression expression) {
|
| + if (expression is NamedExpression) {
|
| + expression = (expression as NamedExpression).expression;
|
| + }
|
| +
|
| + // Curly body functions are.
|
| + if (expression is! FunctionExpression) return false;
|
| + var function = expression as FunctionExpression;
|
| + return function.body is BlockFunctionBody;
|
| + }
|
| +}
|
| +
|
| +/// A range of arguments from a complete argument list.
|
| +///
|
| +/// One of these typically covers all of the arguments in an invocation. But,
|
| +/// when an argument list has block functions in the middle, the arguments
|
| +/// before and after the functions are treated as separate independent lists.
|
| +/// In that case, there will be two of these.
|
| +class ArgumentSublist {
|
| + /// The full argument list from the AST.
|
| + final List<Expression> _allArguments;
|
| +
|
| + /// The positional arguments, in order.
|
| + final List<Expression> _positional;
|
| +
|
| + /// The named arguments, in order.
|
| + final List<Expression> _named;
|
| +
|
| + /// The arguments that are collection literals that get special formatting.
|
| + final Set<Expression> _collections;
|
| +
|
| + /// The number of leading collections.
|
| + ///
|
| + /// If all arguments are collections, this counts them.
|
| + final int _leadingCollections;
|
| +
|
| + /// The number of trailing collections.
|
| + ///
|
| + /// If all arguments are collections, this is zero.
|
| + final int _trailingCollections;
|
| +
|
| + /// The rule used to split the bodies of all of the collection arguments.
|
| + Rule get _collectionRule {
|
| + // Lazy initialize.
|
| + if (_collectionRuleField == null && _collections.isNotEmpty) {
|
| + _collectionRuleField = new SimpleRule(cost: Cost.splitCollections);
|
| + }
|
| +
|
| + return _collectionRuleField;
|
| + }
|
| +
|
| + Rule _collectionRuleField;
|
| +
|
| + bool get _hasMultipleArguments => _positional.length + _named.length > 1;
|
| +
|
| + factory ArgumentSublist(
|
| + List<Expression> allArguments, List<Expression> arguments) {
|
| + // Assumes named arguments follow all positional ones.
|
| + var positional =
|
| + arguments.takeWhile((arg) => arg is! NamedExpression).toList();
|
| + var named = arguments.skip(positional.length).toList();
|
| +
|
| + var collections = arguments.where(_isCollectionArgument).toSet();
|
| +
|
| + // Count the leading arguments that are collection literals.
|
| + var leadingCollections = 0;
|
| + for (var argument in arguments) {
|
| + if (!collections.contains(argument)) break;
|
| + leadingCollections++;
|
| + }
|
| +
|
| + // Count the trailing arguments that are collection literals.
|
| + var trailingCollections = 0;
|
| + if (leadingCollections != arguments.length) {
|
| + for (var argument in arguments.reversed) {
|
| + if (!collections.contains(argument)) break;
|
| + trailingCollections++;
|
| + }
|
| + }
|
| +
|
| + // If only some of the named arguments are collections, treat none of them
|
| + // specially. Avoids cases like:
|
| + //
|
| + // function(
|
| + // a: arg,
|
| + // b: [
|
| + // ...
|
| + // ]);
|
| + if (trailingCollections < named.length) trailingCollections = 0;
|
| +
|
| + // Collections must all be a prefix or suffix of the argument list (and not
|
| + // both).
|
| + if (leadingCollections != collections.length) leadingCollections = 0;
|
| + if (trailingCollections != collections.length) trailingCollections = 0;
|
| +
|
| + // Ignore any collections in the middle of the argument list.
|
| + if (leadingCollections == 0 && trailingCollections == 0) {
|
| + collections.clear();
|
| + }
|
| +
|
| + return new ArgumentSublist._(allArguments, positional, named, collections,
|
| + leadingCollections, trailingCollections);
|
| + }
|
| +
|
| + ArgumentSublist._(this._allArguments, this._positional, this._named,
|
| + this._collections, this._leadingCollections, this._trailingCollections);
|
| +
|
| + void visit(SourceVisitor visitor) {
|
| + var rule = _visitPositional(visitor);
|
| + _visitNamed(visitor, rule);
|
| + }
|
| +
|
| /// Writes the positional arguments, if any.
|
| - PositionalRule _writePositional() {
|
| + PositionalRule _visitPositional(SourceVisitor visitor) {
|
| if (_positional.isEmpty) return null;
|
|
|
| // Allow splitting after "(".
|
| var rule;
|
| if (_positional.length == 1) {
|
| - rule = new SinglePositionalRule(_blockArgumentRule);
|
| + rule = new SinglePositionalRule(_collectionRule,
|
| + splitsOnInnerRules: _allArguments.length > 1 &&
|
| + !_isCollectionArgument(_positional.first));
|
| } else {
|
| // Only count the positional bodies in the positional rule.
|
| - var leadingPositional = _leadingBlockArguments;
|
| - if (_leadingBlockArguments == _node.arguments.length) {
|
| + var leadingPositional = _leadingCollections;
|
| + if (_leadingCollections == _positional.length + _named.length) {
|
| leadingPositional -= _named.length;
|
| }
|
|
|
| - var trailingPositional = _trailingBlockArguments - _named.length;
|
| + var trailingPositional = _trailingCollections - _named.length;
|
| rule = new MultiplePositionalRule(
|
| - _blockArgumentRule, leadingPositional, trailingPositional);
|
| + _collectionRule, leadingPositional, trailingPositional);
|
| }
|
|
|
| - _visitor.builder.startRule(rule);
|
| - rule.beforeArgument(_visitor.zeroSplit());
|
| + visitor.builder.startRule(rule);
|
| +
|
| + var chunk;
|
| + if (_isFirstArgument(_positional.first)) {
|
| + chunk = visitor.zeroSplit();
|
| + } else {
|
| + chunk = visitor.split();
|
| + }
|
| + rule.beforeArgument(chunk);
|
|
|
| // Try to not split the arguments.
|
| - _visitor.builder.startSpan(Cost.positionalArguments);
|
| + visitor.builder.startSpan(Cost.positionalArguments);
|
|
|
| for (var argument in _positional) {
|
| - _writeArgument(rule, argument);
|
| + _visitArgument(visitor, rule, argument);
|
|
|
| // Positional arguments split independently.
|
| if (argument != _positional.last) {
|
| - rule.beforeArgument(_visitor.split());
|
| + rule.beforeArgument(visitor.split());
|
| }
|
| }
|
|
|
| - _visitor.builder.endSpan();
|
| - _visitor.builder.endRule();
|
| + visitor.builder.endSpan();
|
| + visitor.builder.endRule();
|
|
|
| return rule;
|
| }
|
|
|
| /// Writes the named arguments, if any.
|
| - void _writeNamed(PositionalRule rule) {
|
| + void _visitNamed(SourceVisitor visitor, PositionalRule rule) {
|
| if (_named.isEmpty) return;
|
|
|
| var positionalRule = rule;
|
| - var namedRule = new NamedRule(_blockArgumentRule);
|
| - _visitor.builder.startRule(namedRule);
|
| + var namedRule = new NamedRule(_collectionRule);
|
| + visitor.builder.startRule(namedRule);
|
|
|
| // Let the positional args force the named ones to split.
|
| if (positionalRule != null) {
|
| @@ -209,82 +333,65 @@ class ArgumentListVisitor {
|
| }
|
|
|
| // Split before the first named argument.
|
| - namedRule
|
| - .beforeArguments(_visitor.builder.split(space: _positional.isNotEmpty));
|
| + namedRule.beforeArguments(visitor.builder.split(
|
| + space: !_isFirstArgument(_named.first)));
|
|
|
| for (var argument in _named) {
|
| - _writeArgument(namedRule, argument);
|
| + _visitArgument(visitor, namedRule, argument);
|
|
|
| // Write the split.
|
| - if (argument != _named.last) _visitor.split();
|
| + if (argument != _named.last) visitor.split();
|
| }
|
|
|
| - _visitor.builder.endRule();
|
| + visitor.builder.endRule();
|
| }
|
|
|
| - void _writeArgument(ArgumentRule rule, Expression argument) {
|
| - // If we're about to write a block argument, handle it specially.
|
| - if (_blockArguments.contains(argument)) {
|
| - if (rule != null) rule.beforeBlockArgument();
|
| + void _visitArgument(SourceVisitor visitor, ArgumentRule rule, Expression argument) {
|
| + // If we're about to write a collection argument, handle it specially.
|
| + if (_collections.contains(argument)) {
|
| + if (rule != null) rule.beforeCollection();
|
|
|
| // Tell it to use the rule we've already created.
|
| - _visitor.setNextLiteralBodyRule(_blockArgumentRule);
|
| - } else if (_node.arguments.length > 1) {
|
| + visitor.setNextLiteralBodyRule(_collectionRule);
|
| + } else if (_hasMultipleArguments) {
|
| // Corner case: If there is just a single argument, don't bump the
|
| // nesting. This lets us avoid spurious indentation in cases like:
|
| //
|
| // function(function(() {
|
| // body;
|
| // }));
|
| - _visitor.builder.startBlockArgumentNesting();
|
| + visitor.builder.startBlockArgumentNesting();
|
| }
|
|
|
| - _visitor.visit(argument);
|
| + visitor.visit(argument);
|
|
|
| - if (_blockArguments.contains(argument)) {
|
| - if (rule != null) rule.afterBlockArgument();
|
| - } else if (_node.arguments.length > 1) {
|
| - _visitor.builder.endBlockArgumentNesting();
|
| + if (_collections.contains(argument)) {
|
| + if (rule != null) rule.afterCollection();
|
| + } else if (_hasMultipleArguments) {
|
| + visitor.builder.endBlockArgumentNesting();
|
| }
|
|
|
| // Write the trailing comma.
|
| - if (argument != _node.arguments.last) {
|
| - _visitor.token(argument.endToken.next);
|
| + if (!_isLastArgument(argument)) {
|
| + visitor.token(argument.endToken.next);
|
| }
|
| }
|
|
|
| - /// Returns true if [expression] denotes a block argument.
|
| + bool _isFirstArgument(Expression argument) => argument == _allArguments.first;
|
| +
|
| + bool _isLastArgument(Expression argument) => argument == _allArguments.last;
|
| +
|
| + /// Returns true if [expression] denotes a collection literal argument.
|
| ///
|
| - /// That means a collection literal or a function expression with a block
|
| - /// body. Block arguments can get special indentation to make them look more
|
| - /// statement-like.
|
| - static bool _isBlockArgument(Expression expression) {
|
| + /// Similar to block functions, collection arguments can get special
|
| + /// indentation to make them look more statement-like.
|
| + static bool _isCollectionArgument(Expression expression) {
|
| if (expression is NamedExpression) {
|
| expression = (expression as NamedExpression).expression;
|
| }
|
|
|
| // TODO(rnystrom): Should we step into parenthesized expressions?
|
|
|
| - // Collections are bodies.
|
| - if (expression is ListLiteral) return true;
|
| - if (expression is MapLiteral) return true;
|
| -
|
| - // Curly body functions are.
|
| - if (expression is! FunctionExpression) return false;
|
| - var function = expression as FunctionExpression;
|
| - return function.body is BlockFunctionBody;
|
| - }
|
| -
|
| - /// Returns `true` if [expression] is a [FunctionExpression] with a block
|
| - /// body.
|
| - static bool _isBlockFunction(Expression expression) {
|
| - if (expression is NamedExpression) {
|
| - expression = (expression as NamedExpression).expression;
|
| - }
|
| -
|
| - // Curly body functions are.
|
| - if (expression is! FunctionExpression) return false;
|
| - var function = expression as FunctionExpression;
|
| - return function.body is BlockFunctionBody;
|
| + return expression is ListLiteral || expression is MapLiteral;
|
| }
|
| -}
|
| +}
|
|
|