Skip to content

Commit 156f5c8

Browse files
authored
Split metadata on parameters independently. (#1217)
Also, don't indent parameters with metadata annotations. Before this change, the formatter indents any parameter that contains metadata annotations that end up splitting: function( @required int n, @deprecated('Some long deprecation string.') String s) { ... } It also treats all parameters in a parameter list as a unit when deciding how to split their metadata. If any annotation in any parameter splits, then they all do. That's why there's a split after `@required` in the above example. The intent of both of these is so that a parameter with unsplit metadata doesn't get confused for an annotation on the next parameter: function( @FIRST('some string') int n, @second('another string forces a split') String s) { ... } Note here that there are two parameters, `n`, and `s`, and not just a single parameter `s` with two annotations. Unfortunately line splitting all of the parameters as a unit is very bad for performance when there is a large number of parameters (#1212). Also, it's not very helpful. In practice, parameter metadata is rare and most parameters that have any annotations only have one. And the indentation is just strange looking and inconsistent with how annotations are formatted elsewhere. It also means that parameters with split metadata don't align with parameters that don't have metadata. This change determines whether each parameter's annotations should split independently from the other parameters' and removes that indentation. The above example becomes: function( @required int n, @deprecated('Some long deprecation string.') String s) { ... } This improves performance on large parameter lists and I think looks better on real-world examples. I ran it on a large corpus (2,112,352 lines in 6,911 files) and I think the impact is small enough to not go through the full change process: 293 insertions + 443 deletions = 736 changes 1 changed line for every 2870.04 lines of code 0.3484 changed lines for every 1,000 lines of code The full diff is: https://gist.github.com/munificent/1dc7361438934a3587f6149049682e29 Fix #1212.
1 parent 6b4d761 commit 156f5c8

File tree

9 files changed

+182
-92
lines changed

9 files changed

+182
-92
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# 2.3.2-dev
22

3+
* Don't indent parameters that have metadata annotations. Instead, align them
4+
with the metadata and other parameters.
5+
* Allow metadata annotations on parameters to split independently of annotations
6+
on other parameters (#1212).
37
* Don't split before `.` following a record literal (#1213).
48
* Don't force split on a line comment before a switch expression case (#1215).
59

lib/src/chunk_builder.dart

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,6 @@ class ChunkBuilder {
6060
/// Whether the next chunk should be flush left.
6161
bool _pendingFlushLeft = false;
6262

63-
/// Whether subsequent hard splits should be allowed to divide for line
64-
/// splitting.
65-
///
66-
/// Most rules used by multiple chunks will never have a hard split on a
67-
/// chunk between two chunks using that rule. That means when we see a hard
68-
/// split, we can line split the chunks before and after it independently.
69-
///
70-
/// However, there are a couple of places where a single rule spans
71-
/// multiple chunks where hard splits also appear interleaved between them.
72-
/// Currently, that's the rule for splitting switch case bodies, and the
73-
/// rule for parameter list metadata.
74-
///
75-
/// In those circumstances, we mark the chunk with the hard split as not
76-
/// being allowed to divide. That way, all of the chunks using the rule are
77-
/// split together.
78-
bool _pendingPreventDivide = false;
79-
8063
/// Whether the most recently written output was a comment.
8164
bool _afterComment = false;
8265

@@ -173,7 +156,6 @@ class ChunkBuilder {
173156

174157
_nesting.commitNesting();
175158
_afterComment = false;
176-
_pendingPreventDivide = false;
177159
}
178160

179161
/// Writes one or two hard newlines.
@@ -186,14 +168,10 @@ class ChunkBuilder {
186168
/// nesting. If [nest] is `true` then the next line will use expression
187169
/// nesting.
188170
void writeNewline(
189-
{bool isDouble = false,
190-
bool flushLeft = false,
191-
bool nest = false,
192-
bool preventDivide = false}) {
171+
{bool isDouble = false, bool flushLeft = false, bool nest = false}) {
193172
_pendingNewlines = isDouble ? 2 : 1;
194173
_pendingFlushLeft = flushLeft;
195174
_pendingNested = nest;
196-
_pendingPreventDivide |= preventDivide;
197175
}
198176

199177
/// Writes a space before the subsequent non-whitespace text.
@@ -877,11 +855,7 @@ class ChunkBuilder {
877855
isHard: isHard, isDouble: isDouble, space: space);
878856
}
879857

880-
if (chunk.rule.isHardened) {
881-
_handleHardSplit();
882-
883-
if (_pendingPreventDivide) chunk.preventDivide();
884-
}
858+
if (chunk.rule.isHardened) _handleHardSplit();
885859

886860
_pendingNewlines = 0;
887861
_pendingNested = false;

lib/src/source_visitor.dart

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,6 @@ class SourceVisitor extends ThrowingAstVisitor {
8181
/// split.
8282
final List<bool> _collectionSplits = [];
8383

84-
/// The stack of current rules for handling parameter metadata.
85-
///
86-
/// Each time a parameter (or type parameter) list is begun, a single rule
87-
/// for all of the metadata annotations on parameters in that list is pushed
88-
/// onto this stack. We reuse this rule for all annotations so that they split
89-
/// in unison.
90-
final List<Rule> _metadataRules = [];
91-
9284
/// The mapping for blocks that are managed by the argument list that contains
9385
/// them.
9486
///
@@ -1343,8 +1335,6 @@ class SourceVisitor extends ThrowingAstVisitor {
13431335
if (nestExpression) builder.nestExpression();
13441336
token(node.leftParenthesis);
13451337

1346-
_metadataRules.add(Rule());
1347-
13481338
PositionalRule? rule;
13491339
if (requiredParams.isNotEmpty) {
13501340
rule = PositionalRule(null, argumentCount: requiredParams.length);
@@ -1405,8 +1395,6 @@ class SourceVisitor extends ThrowingAstVisitor {
14051395
token(node.rightDelimiter);
14061396
}
14071397

1408-
_metadataRules.removeLast();
1409-
14101398
token(node.rightParenthesis);
14111399
if (nestExpression) builder.unnest();
14121400
}
@@ -2507,7 +2495,6 @@ class SourceVisitor extends ThrowingAstVisitor {
25072495
return;
25082496
}
25092497

2510-
_metadataRules.add(Rule());
25112498
token(node.leftParenthesis);
25122499
builder.startRule();
25132500

@@ -2562,7 +2549,6 @@ class SourceVisitor extends ThrowingAstVisitor {
25622549

25632550
builder = builder.endBlock(forceSplit: force);
25642551
builder.endRule();
2565-
_metadataRules.removeLast();
25662552

25672553
// Now write the delimiter(s) themselves.
25682554
_writeText(firstClosingDelimiter.lexeme, firstClosingDelimiter);
@@ -2983,11 +2969,7 @@ class SourceVisitor extends ThrowingAstVisitor {
29832969

29842970
@override
29852971
void visitTypeParameterList(TypeParameterList node) {
2986-
_metadataRules.add(Rule());
2987-
29882972
_visitGenericList(node.leftBracket, node.rightBracket, node.typeParameters);
2989-
2990-
_metadataRules.removeLast();
29912973
}
29922974

29932975
@override
@@ -3135,24 +3117,12 @@ class SourceVisitor extends ThrowingAstVisitor {
31353117
return;
31363118
}
31373119

3138-
// Split before all of the annotations or none.
3139-
builder.startLazyRule(_metadataRules.last);
3120+
// Split before all of the annotations on this parameter or none of them.
3121+
builder.startLazyRule();
31403122

3141-
visitNodes(metadata, between: split, after: () {
3142-
// Don't nest until right before the last metadata. Ensures we only
3143-
// indent the parameter and not any of the metadata:
3144-
//
3145-
// function(
3146-
// @LongAnnotation
3147-
// @LongAnnotation
3148-
// indentedParameter) {}
3149-
builder.nestExpression(now: true);
3150-
split();
3151-
});
3123+
visitNodes(metadata, between: split, after: split);
31523124
visitParameter();
31533125

3154-
builder.unnest();
3155-
31563126
// Wrap the rule around the parameter too. If it splits, we want to force
31573127
// the annotations to split as well.
31583128
builder.endRule();
@@ -3672,8 +3642,6 @@ class SourceVisitor extends ThrowingAstVisitor {
36723642
// Can't have a trailing comma if there are no parameters.
36733643
assert(parameters.parameters.isNotEmpty);
36743644

3675-
_metadataRules.add(Rule());
3676-
36773645
// Always split the parameters.
36783646
builder.startRule(Rule.hard());
36793647

@@ -3698,7 +3666,7 @@ class SourceVisitor extends ThrowingAstVisitor {
36983666
builder = builder.startBlock();
36993667

37003668
for (var parameter in parameters.parameters) {
3701-
builder.writeNewline(preventDivide: true);
3669+
builder.writeNewline();
37023670
visit(parameter);
37033671
_writeCommaAfter(parameter);
37043672

@@ -3715,15 +3683,13 @@ class SourceVisitor extends ThrowingAstVisitor {
37153683
var firstDelimiter =
37163684
parameters.rightDelimiter ?? parameters.rightParenthesis;
37173685
if (firstDelimiter.precedingComments != null) {
3718-
builder.writeNewline(preventDivide: true);
3686+
builder.writeNewline();
37193687
writePrecedingCommentsAndNewlines(firstDelimiter);
37203688
}
37213689

37223690
builder = builder.endBlock();
37233691
builder.endRule();
37243692

3725-
_metadataRules.removeLast();
3726-
37273693
// Now write the delimiter itself.
37283694
_writeText(firstDelimiter.lexeme, firstDelimiter);
37293695
if (firstDelimiter != parameters.rightParenthesis) {

test/comments/functions.unit

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,17 +235,16 @@ function({
235235
}) {
236236
;
237237
}
238-
>>> metadata in trailing comma parameter list splits together even with comment
238+
>>> splitting in none parameter's metadata doesn't force others to split
239239
function(@Annotation longParameter,
240240
// Comment.
241241
@Annotation @Other @Third longParameter2,) {}
242242
<<<
243243
function(
244-
@Annotation
245-
longParameter,
244+
@Annotation longParameter,
246245
// Comment.
247246
@Annotation
248247
@Other
249248
@Third
250-
longParameter2,
249+
longParameter2,
251250
) {}

test/regression/0200/0247.unit

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
<<<
1111
init(
1212
{@Option(help: 'The git Uri containing the jefe.yaml.', abbr: 'g')
13-
String gitUri,
13+
String gitUri,
1414
@Option(help: 'The directory to install into', abbr: 'd')
15-
String installDirectory: '.',
15+
String installDirectory: '.',
1616
@Flag(help: 'Skips the checkout of the develop branch', abbr: 's')
17-
bool skipCheckout: false}) async {}
17+
bool skipCheckout: false}) async {}

test/regression/0300/0387.unit

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@ greet(@Rest(valueHelp: 'who', help: 'Name(s) to greet.') List<String> who,
77
@Option(name: 'greeting', help: 'Alternate word to greet with e.g. "Hi".')
88
String salutation: 'Hello'}) {}
99
<<<
10-
greet(
11-
@Rest(valueHelp: 'who', help: 'Name(s) to greet.')
12-
List<String> who,
10+
greet(@Rest(valueHelp: 'who', help: 'Name(s) to greet.') List<String> who,
1311
{@Group.start(title: 'Output')
1412
@Option(help: 'How many !\'s to append.')
15-
int enthusiasm: 0,
16-
@Flag(abbr: 'l', help: 'Put names on separate lines.')
17-
bool lineMode: false,
13+
int enthusiasm: 0,
14+
@Flag(abbr: 'l', help: 'Put names on separate lines.') bool lineMode: false,
1815
@Option(name: 'greeting', help: 'Alternate word to greet with e.g. "Hi".')
19-
String salutation: 'Hello'}) {}
16+
String salutation: 'Hello'}) {}

test/regression/0400/0444.unit

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ class MyComponent {
1717
Object secondArgument;
1818

1919
MyComponent(
20-
@Inject(const MyFirstArgument())
21-
this.firstArgument,
20+
@Inject(const MyFirstArgument()) this.firstArgument,
2221
@Inject(const MySuperDuperLongNamedArgument())
23-
this.superDuperLongNamedArgument, // LOOK AT ME
24-
@Inject(const MySecondArgument())
25-
this.secondArgument);
22+
this.superDuperLongNamedArgument, // LOOK AT ME
23+
@Inject(const MySecondArgument()) this.secondArgument);
2624
}

0 commit comments

Comments
 (0)