Format list pattern and rest pattern. (#1363)
* Format list pattern.
* Add documentation for PatternExtensions.canBlockSplit.
* Fix comment in if statement.
* Fix comment nits.
diff --git a/lib/src/ast_extensions.dart b/lib/src/ast_extensions.dart
index 566cf1a..92434e6 100644
--- a/lib/src/ast_extensions.dart
+++ b/lib/src/ast_extensions.dart
@@ -313,3 +313,15 @@
return true;
}
}
+
+extension PatternExtensions on DartPattern {
+ /// Whether this expression is a non-empty delimited container for inner
+ /// expressions that allows "block-like" formatting in some contexts.
+ ///
+ /// See [ExpressionExtensions.canBlockSplit].
+ bool get canBlockSplit => switch (this) {
+ ListPattern(:var elements, :var rightBracket) =>
+ elements.canSplit(rightBracket),
+ _ => false,
+ };
+}
diff --git a/lib/src/front_end/ast_node_visitor.dart b/lib/src/front_end/ast_node_visitor.dart
index 070f0a7..00c863c 100644
--- a/lib/src/front_end/ast_node_visitor.dart
+++ b/lib/src/front_end/ast_node_visitor.dart
@@ -964,9 +964,22 @@
var expressionPiece = nodePiece(ifStatement.expression);
if (ifStatement.caseClause case var caseClause?) {
var caseClausePiece = nodePiece(caseClause);
+ // If the case clause can have block formatting, then a newline in
+ // it doesn't force the if-case to split before the `case` keyword,
+ // like:
+ //
+ // if (obj case [
+ // first,
+ // second,
+ // third,
+ // ]) {
+ // ;
+ // }
+ var allowInnerSplit = caseClause.guardedPattern.pattern.canBlockSplit;
b.add(AssignPiece(
expressionPiece,
caseClausePiece,
+ allowInnerSplit: allowInnerSplit,
indentInValue: true,
));
} else {
@@ -1138,7 +1151,7 @@
@override
Piece visitListLiteral(ListLiteral node) {
return createCollection(
- node.constKeyword,
+ constKeyword: node.constKeyword,
typeArguments: node.typeArguments,
node.leftBracket,
node.elements,
@@ -1148,7 +1161,12 @@
@override
Piece visitListPattern(ListPattern node) {
- throw UnimplementedError();
+ return createCollection(
+ typeArguments: node.typeArguments,
+ node.leftBracket,
+ node.elements,
+ node.rightBracket,
+ );
}
@override
@@ -1405,7 +1423,7 @@
}
return createCollection(
- node.constKeyword,
+ constKeyword: node.constKeyword,
node.leftParenthesis,
node.fields,
node.rightParenthesis,
@@ -1487,7 +1505,10 @@
@override
Piece visitRestPatternElement(RestPatternElement node) {
- throw UnimplementedError();
+ return buildPiece((b) {
+ b.token(node.operator);
+ b.visit(node.pattern);
+ });
}
@override
@@ -1509,7 +1530,7 @@
@override
Piece visitSetOrMapLiteral(SetOrMapLiteral node) {
return createCollection(
- node.constKeyword,
+ constKeyword: node.constKeyword,
typeArguments: node.typeArguments,
node.leftBracket,
node.elements,
diff --git a/lib/src/front_end/delimited_list_builder.dart b/lib/src/front_end/delimited_list_builder.dart
index b3d63f1..8612c96 100644
--- a/lib/src/front_end/delimited_list_builder.dart
+++ b/lib/src/front_end/delimited_list_builder.dart
@@ -157,6 +157,7 @@
var format = switch (element) {
FunctionExpression() when element.canBlockSplit => BlockFormat.function,
Expression() when element.canBlockSplit => BlockFormat.block,
+ DartPattern() when element.canBlockSplit => BlockFormat.block,
_ => BlockFormat.none,
};
diff --git a/lib/src/front_end/piece_factory.dart b/lib/src/front_end/piece_factory.dart
index f24f0e8..f8ab71f 100644
--- a/lib/src/front_end/piece_factory.dart
+++ b/lib/src/front_end/piece_factory.dart
@@ -118,10 +118,15 @@
});
}
- /// Creates a [ListPiece] for a collection literal.
- Piece createCollection(Token? constKeyword, Token leftBracket,
- List<AstNode> elements, Token rightBracket,
- {TypeArgumentList? typeArguments, ListStyle style = const ListStyle()}) {
+ /// Creates a [ListPiece] for a collection literal or pattern.
+ Piece createCollection(
+ Token leftBracket,
+ List<AstNode> elements,
+ Token rightBracket, {
+ Token? constKeyword,
+ TypeArgumentList? typeArguments,
+ ListStyle style = const ListStyle(),
+ }) {
return buildPiece((b) {
b.modifier(constKeyword);
b.visit(typeArguments);
diff --git a/lib/src/piece/assign.dart b/lib/src/piece/assign.dart
index 2169eba..358a581 100644
--- a/lib/src/piece/assign.dart
+++ b/lib/src/piece/assign.dart
@@ -114,7 +114,9 @@
writer.format(target);
writer.splitIf(state == _atOperator);
- if (_indentInValue) {
+
+ // We need extra indentation when there's no inner splitting of the value.
+ if (!_allowInnerSplit && _indentInValue) {
writer.setIndent(Indent.expression * 2);
}
writer.format(value);
diff --git a/test/pattern/list.stmt b/test/pattern/list.stmt
new file mode 100644
index 0000000..354eb0a
--- /dev/null
+++ b/test/pattern/list.stmt
@@ -0,0 +1,91 @@
+40 columns |
+>>> Basic list patterns.
+switch (obj) {
+case [ ] :
+case < int > [ ] :
+case [ 2 ] :
+case [ 2 , ] :
+case [ 2 , 3 ] :
+ ok;
+}
+<<<
+switch (obj) {
+ case []:
+ case <int>[]:
+ case [2]:
+ case [2]:
+ case [2, 3]:
+ ok;
+}
+>>> Unsplit list.
+if (obj case [1, ...var x, 3]) {;}
+<<<
+if (obj case [1, ...var x, 3]) {
+ ;
+}
+>>> If it splits anywhere in the list, it splits at every element.
+if (obj case [first,second,third,fourth]) {;}
+<<<
+if (obj case [
+ first,
+ second,
+ third,
+ fourth,
+]) {
+ ;
+}
+>>> Unsplit short list even with a comma.
+if (obj case [1,]) {;}
+<<<
+if (obj case [1]) {
+ ;
+}
+>>> Nested list patterns don't force outer to split
+if (obj case [[1, 2], [[3]]]) {;}
+<<<
+if (obj case [[1, 2], [[3]]]) {
+ ;
+}
+>>> Split all elements and keep line comment on newline.
+if (obj case [
+ // yeah
+ a,b,c,
+ d,e,f,
+]) {;}
+<<<
+if (obj case [
+ // yeah
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+]) {
+ ;
+}
+>>> Split in type argument, but not in the body.
+if (obj case <Map<VeryLongTypeArgument, VeryLongTypeArgument>>[e]) {;}
+<<<
+if (obj case <
+ Map<
+ VeryLongTypeArgument,
+ VeryLongTypeArgument
+ >
+>[e]) {
+ ;
+}
+>>> Split in type argument and body.
+if (obj case <Map<VeryLongTypeArgument, VeryLongTypeArgument>>[element,VeryLongElementElementElement]) {;}
+<<<
+if (obj case <
+ Map<
+ VeryLongTypeArgument,
+ VeryLongTypeArgument
+ >
+>[
+ element,
+ VeryLongElementElementElement,
+]) {
+ ;
+}