blob: 61e820b621ff1f387b43add6157d1d613760ae84 [file] [log] [blame]
// Copyright (c) 2014, 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.
library dart_style.src.argument_list_visitor;
import 'package:analyzer/analyzer.dart';
import 'chunk.dart';
import 'rule/argument.dart';
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.
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 number of leading block arguments.
///
/// If all arguments are block arguments, this counts them.
final int _leadingBlockArguments;
/// The number of trailing block arguments.
///
/// 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;
/// Returns `true` if there is only a single positional argument.
bool get _isSingle => _positional.length == 1 && _named.isEmpty;
/// 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 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;
// Corner case: If there is just a single argument, don't bump the nesting.
// This lets us avoid spurious indentation in cases like:
//
// object.method(function(() {
// body;
// }));
return _node.arguments.length > 1;
}
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++;
}
}
// 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();
}
return new ArgumentListVisitor._(visitor, node, positional, named, blocks,
leadingBlocks, trailingBlocks);
}
ArgumentListVisitor._(
this._visitor,
this._node,
this._positional,
this._named,
this._blockArguments,
this._leadingBlockArguments,
this._trailingBlockArguments);
/// Builds chunks for the call chain.
void visit() {
// If there is just one positional argument, it tends to look weird to
// split before it, so try not to.
if (_isSingle) _visitor.builder.startSpan();
// Nest around the parentheses in case there are comments before or after
// them.
_visitor.builder.nestExpression();
_visitor.builder.startSpan();
_visitor.token(_node.leftParenthesis);
var rule = _writePositional();
_writeNamed(rule);
_visitor.token(_node.rightParenthesis);
_visitor.builder.endSpan();
_visitor.builder.unnest();
if (_isSingle) _visitor.builder.endSpan();
}
/// Writes the positional arguments, if any.
PositionalRule _writePositional() {
if (_positional.isEmpty) return null;
// Allow splitting after "(".
var rule;
if (_positional.length == 1) {
rule = new SinglePositionalRule(_blockArgumentRule);
} else {
// Only count the positional bodies in the positional rule.
var leadingPositional = _leadingBlockArguments;
if (_leadingBlockArguments == _node.arguments.length) {
leadingPositional -= _named.length;
}
var trailingPositional = _trailingBlockArguments - _named.length;
rule = new MultiplePositionalRule(
_blockArgumentRule, leadingPositional, trailingPositional);
}
_visitor.builder.startRule(rule);
rule.beforeArgument(_visitor.zeroSplit());
// Try to not split the arguments.
_visitor.builder.startSpan(Cost.positionalArguments);
for (var argument in _positional) {
_writeArgument(rule, argument);
// Positional arguments split independently.
if (argument != _positional.last) {
rule.beforeArgument(_visitor.split());
}
}
_visitor.builder.endSpan();
_visitor.builder.endRule();
return rule;
}
/// Writes the named arguments, if any.
void _writeNamed(PositionalRule rule) {
if (_named.isEmpty) return;
var positionalRule = rule;
var namedRule = new NamedRule(_blockArgumentRule);
_visitor.builder.startRule(namedRule);
// Let the positional args force the named ones to split.
if (positionalRule != null) {
positionalRule.setNamedArgsRule(namedRule);
}
// Split before the first named argument.
namedRule
.beforeArguments(_visitor.builder.split(space: _positional.isNotEmpty));
for (var argument in _named) {
_writeArgument(namedRule, argument);
// Write the split.
if (argument != _named.last) _visitor.split();
}
_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();
// Tell it to use the rule we've already created.
_visitor.setNextLiteralBodyRule(_blockArgumentRule);
} else if (_node.arguments.length > 1) {
// 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.visit(argument);
if (_blockArguments.contains(argument)) {
if (rule != null) rule.afterBlockArgument();
} else if (_node.arguments.length > 1) {
_visitor.builder.endBlockArgumentNesting();
}
// Write the trailing comma.
if (argument != _node.arguments.last) {
_visitor.token(argument.endToken.next);
}
}
/// Returns true if [expression] denotes a block 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) {
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;
}
}