Support generic methods. Fix #556.
R=kevmoo@google.com
Review URL: https://codereview.chromium.org//2526633002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dfaf086..3bc7c37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
* Add support for assert() in constructor initializers.
* Correctly indent the right-hand side of `is` and `as` expressions.
* Avoid splitting in index operators when possible.
+* Support generic methods (#556).
# 0.2.11+1
diff --git a/lib/src/argument_list_visitor.dart b/lib/src/argument_list_visitor.dart
index 843d757..38bc6eb 100644
--- a/lib/src/argument_list_visitor.dart
+++ b/lib/src/argument_list_visitor.dart
@@ -153,9 +153,6 @@
// 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(_leftParenthesis);
@@ -193,8 +190,6 @@
_visitor.token(_rightParenthesis);
- _visitor.builder.unnest();
-
if (_isSingle) _visitor.builder.endSpan();
}
diff --git a/lib/src/call_chain_visitor.dart b/lib/src/call_chain_visitor.dart
index 29c04fb..e1fec05 100644
--- a/lib/src/call_chain_visitor.dart
+++ b/lib/src/call_chain_visitor.dart
@@ -317,7 +317,7 @@
// If the expression ends in an argument list, base the splitting on the
// last argument.
- var argumentList;
+ ArgumentList argumentList;
if (expression is MethodInvocation) {
argumentList = expression.argumentList;
} else if (expression is InstanceCreationExpression) {
@@ -331,7 +331,9 @@
if (argumentList.arguments.isEmpty) return true;
var argument = argumentList.arguments.last;
- if (argument is NamedExpression) argument = argument.expression;
+ if (argument is NamedExpression) {
+ argument = (argument as NamedExpression).expression;
+ }
// TODO(rnystrom): This logic is similar (but not identical) to
// ArgumentListVisitor.hasBlockArguments. They overlap conceptually and
@@ -399,7 +401,10 @@
_endSpan();
}
- _visitor.visit(invocation.argumentList);
+ _visitor.builder.nestExpression();
+ _visitor.visit(invocation.typeArguments);
+ _visitor.visitArgumentList(invocation.argumentList, nestExpression: false);
+ _visitor.builder.unnest();
}
void _writeBlockCall(MethodInvocation invocation) {
diff --git a/lib/src/dart_formatter.dart b/lib/src/dart_formatter.dart
index 3269dbe..6c298b1 100644
--- a/lib/src/dart_formatter.dart
+++ b/lib/src/dart_formatter.dart
@@ -102,6 +102,7 @@
// Parse it.
var parser = new Parser(stringSource, errorListener);
+ parser.parseGenericMethods = true;
AstNode node;
if (source.isCompilationUnit) {
diff --git a/lib/src/source_visitor.dart b/lib/src/source_visitor.dart
index 2bc844b..2d2c257 100644
--- a/lib/src/source_visitor.dart
+++ b/lib/src/source_visitor.dart
@@ -133,7 +133,7 @@
/// 4. Split between one or more positional arguments, trying to keep as many
/// on earlier lines as possible.
/// 5. Split the named arguments each onto their own line.
- visitArgumentList(ArgumentList node) {
+ visitArgumentList(ArgumentList node, {bool nestExpression: true}) {
// Corner case: handle empty argument lists.
if (node.arguments.isEmpty) {
token(node.leftParenthesis);
@@ -154,7 +154,9 @@
return;
}
+ if (nestExpression) builder.nestExpression();
new ArgumentListVisitor(this, node).visit();
+ if (nestExpression) builder.unnest();
}
visitAsExpression(AsExpression node) {
@@ -179,9 +181,11 @@
var arguments = <Expression>[node.condition];
if (node.message != null) arguments.add(node.message);
+ builder.nestExpression();
var visitor = new ArgumentListVisitor.forArguments(
this, node.leftParenthesis, node.rightParenthesis, arguments);
visitor.visit();
+ builder.unnest();
});
}
@@ -1220,10 +1224,18 @@
token(node.keyword);
space();
builder.startSpan(Cost.constructorName);
+
+ // Start the expression nesting for the argument list here, in case this
+ // is a generic constructor with type arguments. If it is, we need the type
+ // arguments to be nested too so they get indented past the arguments.
+ builder.nestExpression();
visit(node.constructorName);
+
builder.endSpan();
- visit(node.argumentList);
+ visitArgumentList(node.argumentList, nestExpression: false);
builder.endSpan();
+
+ builder.unnest();
}
visitIntegerLiteral(IntegerLiteral node) {
@@ -1315,8 +1327,22 @@
// This will be non-null for cascade sections.
token(node.operator);
- token(node.methodName.token);
- visit(node.argumentList);
+ visit(node.methodName);
+
+ // TODO(rnystrom): Currently, there are no constraints between a generic
+ // method's type arguments and arguments. That can lead to some funny
+ // splitting like:
+ //
+ // method<VeryLongType,
+ // AnotherTypeArgument>(argument,
+ // argument, argument, argument);
+ //
+ // The indentation is fine, but splitting in the middle of each argument
+ // list looks kind of strange. If this ends up happening in real world
+ // code, consider putting a constraint between them.
+
+ visit(node.typeArguments);
+ visitArgumentList(node.argumentList, nestExpression: false);
builder.unnest();
builder.endSpan();
diff --git a/pubspec.lock b/pubspec.lock
index 6e79c83..bd701ed 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -48,13 +48,13 @@
name: charcode
url: "https://pub.dartlang.org"
source: hosted
- version: "1.1.0"
+ version: "1.1.1"
collection:
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
- version: "1.11.0"
+ version: "1.12.0"
convert:
description:
name: convert
@@ -72,7 +72,7 @@
name: csslib
url: "https://pub.dartlang.org"
source: hosted
- version: "0.13.2+1"
+ version: "0.13.2+2"
glob:
description:
name: glob
@@ -192,13 +192,13 @@
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
- version: "1.3.0"
+ version: "1.3.2"
scheduled_test:
description:
name: scheduled_test
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.6"
+ version: "0.12.6+1"
shelf:
description:
name: shelf
@@ -246,7 +246,7 @@
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
- version: "1.6.8"
+ version: "1.7.0"
stream_channel:
description:
name: stream_channel
@@ -270,7 +270,7 @@
name: test
url: "https://pub.dartlang.org"
source: hosted
- version: "0.12.15+12"
+ version: "0.12.16"
typed_data:
description:
name: typed_data
diff --git a/test/splitting/generic_method_arguments.stmt b/test/splitting/generic_method_arguments.stmt
new file mode 100644
index 0000000..6abf870
--- /dev/null
+++ b/test/splitting/generic_method_arguments.stmt
@@ -0,0 +1,71 @@
+40 columns |
+>>> all fit on one line
+method<A,B,C,D>();
+<<<
+method<A, B, C, D>();
+>>> prefer to split between args even when they all fit on next line
+lengthyMethodName<First, Second, Third>();
+<<<
+lengthyMethodName<First, Second,
+ Third>();
+>>> split before first if needed
+lengthyMethodName<FirstTypeArgumentIsLong, Second>();
+<<<
+lengthyMethodName<
+ FirstTypeArgumentIsLong, Second>();
+>>> split in middle if fit in two lines
+lengthyMethodName<First, Second, Third, Fourth, Fifth, Sixth, Seventh>();
+<<<
+lengthyMethodName<First, Second, Third,
+ Fourth, Fifth, Sixth, Seventh>();
+>>> split one per line if they don't fit in two lines
+lengthyMethodName<First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth>();
+<<<
+lengthyMethodName<
+ First,
+ Second,
+ Third,
+ Fourth,
+ Fifth,
+ Sixth,
+ Seventh,
+ Eighth>();
+>>> prefers to not split at type arguments
+lengthyMethodName<
+ TypeArgument>(valueArgument);
+<<<
+lengthyMethodName<TypeArgument>(
+ valueArgument);
+>>> split both type and value arguments
+lengthyMethodName<First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth>(first, second, third, fourth, fifth, sixth, seventh, eighth);
+<<<
+lengthyMethodName<
+ First,
+ Second,
+ Third,
+ Fourth,
+ Fifth,
+ Sixth,
+ Seventh,
+ Eighth>(
+ first,
+ second,
+ third,
+ fourth,
+ fifth,
+ sixth,
+ seventh,
+ eighth);
+>>> in method chain
+receiver.method<First, Second, Third, Fourth, Fifth>
+ (first, second, third, fourth, fifth)
+.method<First, Second, Third, Fourth, Fifth>
+ (first, second, third, fourth, fifth);
+<<<
+receiver
+ .method<First, Second, Third,
+ Fourth, Fifth>(first,
+ second, third, fourth, fifth)
+ .method<First, Second, Third,
+ Fourth, Fifth>(first,
+ second, third, fourth, fifth);
\ No newline at end of file
diff --git a/test/splitting/type_arguments.stmt b/test/splitting/type_arguments.stmt
index f89429f..fadd0d8 100644
--- a/test/splitting/type_arguments.stmt
+++ b/test/splitting/type_arguments.stmt
@@ -35,4 +35,24 @@
TypeArgument>(valueArgument);
<<<
new SomeClass<TypeArgument>(
- valueArgument);
\ No newline at end of file
+ valueArgument);
+>>> split both type and value arguments
+new SomeClass<First, Second, Third, Fourth, Fifth, Sixth, Seventh, Eighth>(first, second, third, fourth, fifth, sixth, seventh, eighth);
+<<<
+new SomeClass<
+ First,
+ Second,
+ Third,
+ Fourth,
+ Fifth,
+ Sixth,
+ Seventh,
+ Eighth>(
+ first,
+ second,
+ third,
+ fourth,
+ fifth,
+ sixth,
+ seventh,
+ eighth);
\ No newline at end of file
diff --git a/test/whitespace/expressions.stmt b/test/whitespace/expressions.stmt
index 310e7bd..ede2fe7 100644
--- a/test/whitespace/expressions.stmt
+++ b/test/whitespace/expressions.stmt
@@ -134,4 +134,8 @@
function(
named: arg,
another: arg,
-);
\ No newline at end of file
+);
+>>> generic method call
+method <int,String , bool> ();
+<<<
+method<int, String, bool>();
\ No newline at end of file