Skip to content

Commit fb00aab

Browse files
authored
Allow a comment in code to explicitly configure the page width. (#1574)
Allow a comment in code to explicitly configure the page width. If the code being formatted begins with a comment like: ``` // dart format width=123 ``` Then the code is formatted at that width instead of the default width. The intent is that tools that generate code and then format it will put this comment in the generated code. That way, the tool doesn't have to handle the complex logic to find a surrounding analysis_options.yaml file and get the user's project-wide page width configuration. End users may also want to use this comment in rare cases. For example, maybe they have a library that contains a large value of data that looks better as a big wide table but they don't want their entire project to have a wider page width.
1 parent a5ec37d commit fb00aab

File tree

5 files changed

+277
-5
lines changed

5 files changed

+277
-5
lines changed

lib/src/dart_formatter.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import 'short/source_visitor.dart';
2121
import 'source_code.dart';
2222
import 'string_compare.dart' as string_compare;
2323

24+
/// Regular expression that matches a format width comment like:
25+
///
26+
/// // dart format width=123
27+
final RegExp _widthCommentPattern = RegExp(r'^// dart format width=(\d+)$');
28+
2429
/// Dart source code formatter.
2530
final class DartFormatter {
2631
/// The latest Dart language version that can be parsed and formatted by this
@@ -178,8 +183,21 @@ final class DartFormatter {
178183

179184
SourceCode output;
180185
if (experimentFlags.contains(tallStyleExperimentFlag)) {
186+
// Look for a page width comment before the code.
187+
int? pageWidthFromComment;
188+
for (Token? comment = node.beginToken.precedingComments;
189+
comment != null;
190+
comment = comment.next) {
191+
if (_widthCommentPattern.firstMatch(comment.lexeme) case var match?) {
192+
// If integer parsing fails for some reason, the returned `null`
193+
// means we correctly ignore the comment.
194+
pageWidthFromComment = int.tryParse(match[1]!);
195+
break;
196+
}
197+
}
198+
181199
var visitor = AstNodeVisitor(this, lineInfo, unitSourceCode);
182-
output = visitor.run(unitSourceCode, node);
200+
output = visitor.run(unitSourceCode, node, pageWidthFromComment);
183201
} else {
184202
var visitor = SourceVisitor(this, lineInfo, unitSourceCode);
185203
output = visitor.run(node);

lib/src/front_end/ast_node_visitor.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ final class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
6666
///
6767
/// This is the only method that should be called externally. Everything else
6868
/// is effectively private.
69-
SourceCode run(SourceCode source, AstNode node) {
69+
///
70+
/// If there is a `// dart format width=123` comment before the formatted
71+
/// code, then [pageWidthFromComment] is that width.
72+
SourceCode run(SourceCode source, AstNode node, [int? pageWidthFromComment]) {
7073
Profile.begin('AstNodeVisitor.run()');
7174

7275
Profile.begin('AstNodeVisitor build Piece tree');
@@ -123,7 +126,7 @@ final class AstNodeVisitor extends ThrowingAstVisitor<void> with PieceFactory {
123126
Profile.end('AstNodeVisitor build Piece tree');
124127

125128
// Finish writing and return the complete result.
126-
var result = pieces.finish(source, unitPiece);
129+
var result = pieces.finish(source, unitPiece, pageWidthFromComment);
127130

128131
Profile.end('AstNodeVisitor.run()');
129132

lib/src/front_end/piece_writer.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,11 @@ final class PieceWriter {
390390

391391
/// Finishes writing and returns a [SourceCode] containing the final output
392392
/// and updated selection, if any.
393-
SourceCode finish(SourceCode source, Piece rootPiece) {
393+
///
394+
/// If there is a `// dart format width=123` comment before the formatted
395+
/// code, then [pageWidthFromComment] is that width.
396+
SourceCode finish(
397+
SourceCode source, Piece rootPiece, int? pageWidthFromComment) {
394398
if (debug.tracePieceBuilder) {
395399
debug.log(debug.pieceTree(rootPiece));
396400
}
@@ -399,7 +403,8 @@ final class PieceWriter {
399403

400404
var cache = SolutionCache();
401405
var solver = Solver(cache,
402-
pageWidth: _formatter.pageWidth, leadingIndent: _formatter.indent);
406+
pageWidth: pageWidthFromComment ?? _formatter.pageWidth,
407+
leadingIndent: _formatter.indent);
403408
var solution = solver.format(rootPiece);
404409
var output = solution.code.build(source, _formatter.lineEnding);
405410

test/tall/other/format_off.unit

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,4 +193,134 @@ main() {
193193
unformatted + code + // dart format on
194194
after +
195195
region;
196+
}
197+
>>> "dart format off" whitespace must match exactly.
198+
main() {
199+
//dart format off
200+
unformatted + code;
201+
// dart format on
202+
203+
// dart format off
204+
unformatted + code;
205+
// dart format on
206+
207+
// dart format off
208+
unformatted + code;
209+
// dart format on
210+
211+
// dart format off
212+
unformatted + code;
213+
// dart format on
214+
}
215+
<<<
216+
main() {
217+
//dart format off
218+
unformatted + code;
219+
// dart format on
220+
221+
// dart format off
222+
unformatted + code;
223+
// dart format on
224+
225+
// dart format off
226+
unformatted + code;
227+
// dart format on
228+
229+
// dart format off
230+
unformatted + code;
231+
// dart format on
232+
}
233+
>>> "dart format on" whitespace must match exactly.
234+
main() {
235+
// dart format off
236+
// Doesn't actually turn back on:
237+
//dart format on
238+
unformatted + code;
239+
240+
// Does now:
241+
// dart format on
242+
unformatted + code;
243+
244+
// dart format off
245+
// Doesn't actually turn back on:
246+
// dart format on
247+
unformatted + code;
248+
249+
// Does now:
250+
// dart format on
251+
unformatted + code;
252+
253+
// dart format off
254+
// Doesn't actually turn back on:
255+
// dart format on
256+
unformatted + code;
257+
258+
// Does now:
259+
// dart format on
260+
unformatted + code;
261+
262+
// dart format off
263+
// Doesn't actually turn back on:
264+
// dart format on
265+
unformatted + code;
266+
267+
// Does now:
268+
// dart format on
269+
unformatted + code;
270+
}
271+
<<<
272+
main() {
273+
// dart format off
274+
// Doesn't actually turn back on:
275+
//dart format on
276+
unformatted + code;
277+
278+
// Does now:
279+
// dart format on
280+
unformatted + code;
281+
282+
// dart format off
283+
// Doesn't actually turn back on:
284+
// dart format on
285+
unformatted + code;
286+
287+
// Does now:
288+
// dart format on
289+
unformatted + code;
290+
291+
// dart format off
292+
// Doesn't actually turn back on:
293+
// dart format on
294+
unformatted + code;
295+
296+
// Does now:
297+
// dart format on
298+
unformatted + code;
299+
300+
// dart format off
301+
// Doesn't actually turn back on:
302+
// dart format on
303+
unformatted + code;
304+
305+
// Does now:
306+
// dart format on
307+
unformatted + code;
308+
}
309+
>>> Can't be doc comment.
310+
main() {
311+
here + gets + formatted ;
312+
/// dart format off
313+
here + does + too ;
314+
/// dart format on
315+
and + here + does ;
316+
}
317+
<<<
318+
main() {
319+
here + gets + formatted;
320+
321+
/// dart format off
322+
here + does + too;
323+
324+
/// dart format on
325+
and + here + does;
196326
}

test/tall/other/format_width.unit

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
40 columns |
2+
### Tests for the comment to set formatting width.
3+
>>> Comment sets page width.
4+
// dart format width=30
5+
main() {
6+
fitsUnsplitAt40 + butNotAt30;
7+
}
8+
<<<
9+
// dart format width=30
10+
main() {
11+
fitsUnsplitAt40 +
12+
butNotAt30;
13+
}
14+
>>> Comment only takes effect if it appears before code.
15+
main() {
16+
// dart format width=30
17+
fitsUnsplitAt40 + butNotAt30;
18+
}
19+
<<<
20+
main() {
21+
// dart format width=30
22+
fitsUnsplitAt40 + butNotAt30;
23+
}
24+
>>> If there are multiple comments, first one wins.
25+
// dart format width=30
26+
// dart format width=60
27+
main() {
28+
fitsUnsplitAt40 + butNotAt30;
29+
}
30+
<<<
31+
// dart format width=30
32+
// dart format width=60
33+
main() {
34+
fitsUnsplitAt40 +
35+
butNotAt30;
36+
}
37+
>>> Does nothing if width is not an integer.
38+
// dart format width=wat
39+
main() {
40+
fitsUnsplitAt40 + butNotAt30;
41+
}
42+
<<<
43+
// dart format width=wat
44+
main() {
45+
fitsUnsplitAt40 + butNotAt30;
46+
}
47+
>>> Can't have trailing text.
48+
// dart format width=30 some more text
49+
main() {
50+
fitsUnsplitAt40 + butNotAt30;
51+
}
52+
<<<
53+
// dart format width=30 some more text
54+
main() {
55+
fitsUnsplitAt40 + butNotAt30;
56+
}
57+
>>> Whitespace must match exactly.
58+
//dart format width=30
59+
main() {
60+
fitsUnsplitAt40 + butNotAt30;
61+
}
62+
<<<
63+
//dart format width=30
64+
main() {
65+
fitsUnsplitAt40 + butNotAt30;
66+
}
67+
>>> Whitespace must match exactly.
68+
// dart format width=30
69+
main() {
70+
fitsUnsplitAt40 + butNotAt30;
71+
}
72+
<<<
73+
// dart format width=30
74+
main() {
75+
fitsUnsplitAt40 + butNotAt30;
76+
}
77+
>>> Whitespace must match exactly.
78+
// dart format width = 30
79+
main() {
80+
fitsUnsplitAt40 + butNotAt30;
81+
}
82+
<<<
83+
// dart format width = 30
84+
main() {
85+
fitsUnsplitAt40 + butNotAt30;
86+
}
87+
>>> Can't be a doc comment.
88+
/// dart format width=30
89+
main() {
90+
fitsUnsplitAt40 + butNotAt30;
91+
}
92+
<<<
93+
/// dart format width=30
94+
main() {
95+
fitsUnsplitAt40 + butNotAt30;
96+
}
97+
>>> Can't be nested inside another comment.
98+
/* // dart format width=30 */
99+
main() {
100+
fitsUnsplitAt40 + butNotAt30;
101+
}
102+
<<<
103+
/* // dart format width=30 */
104+
main() {
105+
fitsUnsplitAt40 + butNotAt30;
106+
}
107+
>>> Can't be inside a string literal.
108+
var c = '// dart format width=30';
109+
main() {
110+
fitsUnsplitAt40 + butNotAt30;
111+
}
112+
<<<
113+
var c = '// dart format width=30';
114+
main() {
115+
fitsUnsplitAt40 + butNotAt30;
116+
}

0 commit comments

Comments
 (0)