Skip to content

Commit b5ad63e

Browse files
authored
Start formatting patterns and other related syntax. (#1165)
* Start formatting patterns and other related syntax. I'm breaking this up into a series of PRs so it's not overwhelming. Also, I'm going to merge these PRs into a separate "patterns" branch until #1164 is resolved. That way, master isn't broken. This first PR: - Enables parsing patterns. - Does some rudimentary formatting of constant patterns so that the existing switch tests don't break. I'll add more tests for constant patterns in a later PR. - Formats if-case statements and elements. - Formats logic, relational, and parenthesized patterns. * Add missing override annotations. * Update comments.
1 parent 8e47639 commit b5ad63e

File tree

8 files changed

+433
-19
lines changed

8 files changed

+433
-19
lines changed

lib/src/dart_formatter.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ class DartFormatter {
9090
var featureSet = FeatureSet.fromEnableFlags2(
9191
sdkLanguageVersion: Version(2, 19, 0),
9292
flags: [
93+
// TODO(rnystrom): This breaks existing switch cases containing constant
94+
// expressions that aren't valid patterns. See:
95+
// https://github.com/dart-lang/dart_style/issues/1164
96+
'patterns',
9397
'records',
9498
'unnamed-libraries',
9599
],

lib/src/source_visitor.dart

Lines changed: 144 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,10 @@ class SourceVisitor extends ThrowingAstVisitor {
359359
// appears before the first operand.
360360
builder.startLazyRule();
361361

362-
// Flatten out a tree/chain of the same precedence. If we split on this
363-
// precedence level, we will break all of them.
362+
// Flatten out a tree/chain of the same precedence. If we need to split on
363+
// any of them, we split on all of them.
364364
var precedence = node.operator.type.precedence;
365365

366-
@override
367366
void traverse(Expression e) {
368367
if (e is BinaryExpression && e.operator.type.precedence == precedence) {
369368
traverse(e.leftOperand);
@@ -391,6 +390,46 @@ class SourceVisitor extends ThrowingAstVisitor {
391390
builder.endRule();
392391
}
393392

393+
@override
394+
void visitBinaryPattern(BinaryPattern node) {
395+
builder.startSpan();
396+
builder.nestExpression(now: true);
397+
398+
// Start lazily so we don't force the operator to split if a line comment
399+
// appears before the first operand.
400+
builder.startLazyRule();
401+
402+
// Flatten out a tree/chain of the same precedence. If we need to split on
403+
// any of them, we split on all of them.
404+
var precedence = node.operator.type.precedence;
405+
406+
void traverse(DartPattern p) {
407+
if (p is BinaryPattern && p.operator.type.precedence == precedence) {
408+
traverse(p.leftOperand);
409+
410+
space();
411+
token(p.operator);
412+
413+
split();
414+
traverse(p.rightOperand);
415+
} else {
416+
visit(p);
417+
}
418+
}
419+
420+
// Blocks as operands to infix patterns should always nest like regular
421+
// operands.
422+
builder.startBlockArgumentNesting();
423+
424+
traverse(node);
425+
426+
builder.endBlockArgumentNesting();
427+
428+
builder.unnest();
429+
builder.endSpan();
430+
builder.endRule();
431+
}
432+
394433
@override
395434
void visitBlock(Block node) {
396435
// Treat empty blocks specially. In most cases, they are not allowed to
@@ -755,6 +794,12 @@ class SourceVisitor extends ThrowingAstVisitor {
755794
visit(node.uri);
756795
}
757796

797+
@override
798+
void visitConstantPattern(ConstantPattern node) {
799+
token(node.constKeyword, after: space);
800+
visit(node.expression);
801+
}
802+
758803
@override
759804
void visitConstructorDeclaration(ConstructorDeclaration node) {
760805
visitMetadata(node.metadata);
@@ -1774,12 +1819,8 @@ class SourceVisitor extends ThrowingAstVisitor {
17741819

17751820
var hasInnerControlFlow = false;
17761821
for (var element in ifElements) {
1777-
// The condition.
1778-
token(element.ifKeyword);
1779-
space();
1780-
token(element.leftParenthesis);
1781-
visit(element.condition);
1782-
token(element.rightParenthesis);
1822+
_visitIfCondition(element.ifKeyword, element.leftParenthesis,
1823+
element.condition, element.caseClause, element.rightParenthesis);
17831824

17841825
visitChild(element, element.thenElement);
17851826
if (element.thenElement.isControlFlowElement) {
@@ -1822,13 +1863,8 @@ class SourceVisitor extends ThrowingAstVisitor {
18221863

18231864
@override
18241865
void visitIfStatement(IfStatement node) {
1825-
builder.nestExpression();
1826-
token(node.ifKeyword);
1827-
space();
1828-
token(node.leftParenthesis);
1829-
visit(node.condition);
1830-
token(node.rightParenthesis);
1831-
builder.unnest();
1866+
_visitIfCondition(node.ifKeyword, node.leftParenthesis, node.condition,
1867+
node.caseClause, node.rightParenthesis);
18321868

18331869
void visitClause(Statement clause) {
18341870
if (clause is Block || clause is IfStatement) {
@@ -2222,6 +2258,15 @@ class SourceVisitor extends ThrowingAstVisitor {
22222258
token(node.rightParenthesis);
22232259
}
22242260

2261+
@override
2262+
void visitParenthesizedPattern(ParenthesizedPattern node) {
2263+
builder.nestExpression();
2264+
token(node.leftParenthesis);
2265+
visit(node.pattern);
2266+
builder.unnest();
2267+
token(node.rightParenthesis);
2268+
}
2269+
22252270
@override
22262271
void visitPartDirective(PartDirective node) {
22272272
_visitDirectiveMetadata(node);
@@ -2402,6 +2447,13 @@ class SourceVisitor extends ThrowingAstVisitor {
24022447
});
24032448
}
24042449

2450+
@override
2451+
void visitRelationalPattern(RelationalPattern node) {
2452+
token(node.operator);
2453+
space();
2454+
visit(node.operand);
2455+
}
2456+
24052457
@override
24062458
void visitRethrowExpression(RethrowExpression node) {
24072459
token(node.rethrowKeyword);
@@ -2538,7 +2590,6 @@ class SourceVisitor extends ThrowingAstVisitor {
25382590
token(node.colon);
25392591

25402592
builder.indent();
2541-
// TODO(rnystrom): Allow inline cases?
25422593
newline();
25432594

25442595
visitNodes(node.statements, between: oneOrTwoNewlines);
@@ -2552,7 +2603,29 @@ class SourceVisitor extends ThrowingAstVisitor {
25522603
token(node.colon);
25532604

25542605
builder.indent();
2555-
// TODO(rnystrom): Allow inline cases?
2606+
newline();
2607+
2608+
visitNodes(node.statements, between: oneOrTwoNewlines);
2609+
builder.unindent();
2610+
}
2611+
2612+
@override
2613+
void visitSwitchPatternCase(SwitchPatternCase node) {
2614+
_visitLabels(node.labels);
2615+
2616+
token(node.keyword);
2617+
space();
2618+
2619+
builder.startBlockArgumentNesting();
2620+
builder.nestExpression();
2621+
visit(node.guardedPattern.pattern);
2622+
builder.unnest();
2623+
builder.endBlockArgumentNesting();
2624+
2625+
visit(node.guardedPattern.whenClause);
2626+
token(node.colon);
2627+
2628+
builder.indent();
25562629
newline();
25572630

25582631
visitNodes(node.statements, between: oneOrTwoNewlines);
@@ -2711,6 +2784,20 @@ class SourceVisitor extends ThrowingAstVisitor {
27112784
});
27122785
}
27132786

2787+
@override
2788+
void visitWhenClause(WhenClause node) {
2789+
builder.startRule();
2790+
split();
2791+
token(node.whenKeyword);
2792+
space();
2793+
builder.startBlockArgumentNesting();
2794+
builder.nestExpression();
2795+
visit(node.expression);
2796+
builder.unnest();
2797+
builder.endBlockArgumentNesting();
2798+
builder.endRule();
2799+
}
2800+
27142801
@override
27152802
void visitWhileStatement(WhileStatement node) {
27162803
builder.nestExpression();
@@ -3330,6 +3417,45 @@ class SourceVisitor extends ThrowingAstVisitor {
33303417
builder.endRule();
33313418
}
33323419

3420+
/// Visits the `if (<expr> [case <pattern> [when <expr>]])` header of an if
3421+
/// statement or element.
3422+
void _visitIfCondition(Token ifKeyword, Token leftParenthesis,
3423+
AstNode condition, CaseClause? caseClause, Token rightParenthesis) {
3424+
builder.nestExpression();
3425+
token(ifKeyword);
3426+
space();
3427+
token(leftParenthesis);
3428+
3429+
if (caseClause != null) {
3430+
// Wrap the rule for splitting before "case" around the value expression
3431+
// so that if the value splits, we split before "case" too.
3432+
builder.startRule();
3433+
3434+
// Nest the condition so that it indents deeper than the case clause.
3435+
builder.nestExpression();
3436+
}
3437+
3438+
visit(condition);
3439+
3440+
// If-case clause.
3441+
if (caseClause != null) {
3442+
split();
3443+
token(caseClause.caseKeyword);
3444+
space();
3445+
builder.startBlockArgumentNesting();
3446+
builder.nestExpression();
3447+
visit(caseClause.guardedPattern.pattern);
3448+
builder.unnest();
3449+
builder.endBlockArgumentNesting();
3450+
builder.endRule();
3451+
visit(caseClause.guardedPattern.whenClause);
3452+
}
3453+
3454+
token(rightParenthesis);
3455+
if (caseClause != null) builder.unnest();
3456+
builder.unnest();
3457+
}
3458+
33333459
/// Writes the separator between a type annotation and a variable or
33343460
/// parameter. If the preceding type annotation ends in a delimited list of
33353461
/// elements that have block formatting, then we don't split between the

test/comments/patterns.stmt

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
40 columns |
2+
>>> before first operand of logic
3+
if (obj case // c
4+
pattern || otherPattern) {;}
5+
<<<
6+
if (obj
7+
case // c
8+
pattern || otherPattern) {
9+
;
10+
}
11+
>>> before first operand of nested logic
12+
if (obj case pattern || // c
13+
otherPattern && thirdLongPattern) {;}
14+
<<<
15+
if (obj
16+
case pattern || // c
17+
otherPattern &&
18+
thirdLongPattern) {
19+
;
20+
}
21+
>>> after left logic operand (looks weird, but user should move comment)
22+
if (obj case pattern // c
23+
|| otherPattern) {;}
24+
<<<
25+
if (obj
26+
case pattern // c
27+
||
28+
otherPattern) {
29+
;
30+
}
31+
>>> after logic operator
32+
if (obj case pattern || // c
33+
otherPattern) {;}
34+
<<<
35+
if (obj
36+
case pattern || // c
37+
otherPattern) {
38+
;
39+
}
40+
>>> after right logic operand
41+
if (obj case somePattern || otherPattern // c
42+
) {;}
43+
<<<
44+
if (obj
45+
case somePattern ||
46+
otherPattern // c
47+
) {
48+
;
49+
}
50+
>>> after relational operator
51+
if (obj case <= // c
52+
someConstant + anotherLongConstant) {;}
53+
<<<
54+
if (obj
55+
case <= // c
56+
someConstant +
57+
anotherLongConstant) {
58+
;
59+
}
60+
>>> after relational operand
61+
if (obj case <= someConstant + anotherLongConstant // c
62+
) {;}
63+
<<<
64+
if (obj
65+
case <= someConstant +
66+
anotherLongConstant // c
67+
) {
68+
;
69+
}
70+
>>> inside parenthesized
71+
if (obj case ( // c
72+
pattern)) {;}
73+
<<<
74+
if (obj
75+
case ( // c
76+
pattern)) {
77+
;
78+
}

0 commit comments

Comments
 (0)