Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(250)

Side by Side Diff: dart_style/lib/src/source_visitor.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « dart_style/lib/src/source_code.dart ('k') | dart_style/lib/src/whitespace.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library dart_style.src.source_visitor;
6
7 import 'package:analyzer/analyzer.dart';
8 import 'package:analyzer/src/generated/scanner.dart';
9 import 'package:analyzer/src/generated/source.dart';
10
11 import 'argument_list_visitor.dart';
12 import 'call_chain_visitor.dart';
13 import 'chunk.dart';
14 import 'chunk_builder.dart';
15 import 'dart_formatter.dart';
16 import 'rule/argument.dart';
17 import 'rule/combinator.dart';
18 import 'rule/rule.dart';
19 import 'rule/type_argument.dart';
20 import 'source_code.dart';
21 import 'whitespace.dart';
22
23 /// Visits every token of the AST and passes all of the relevant bits to a
24 /// [ChunkBuilder].
25 class SourceVisitor implements AstVisitor {
26 /// The builder for the block that is currently being visited.
27 ChunkBuilder builder;
28
29 final DartFormatter _formatter;
30
31 /// Cached line info for calculating blank lines.
32 LineInfo _lineInfo;
33
34 /// The source being formatted.
35 final SourceCode _source;
36
37 /// `true` if the visitor has written past the beginning of the selection in
38 /// the original source text.
39 bool _passedSelectionStart = false;
40
41 /// `true` if the visitor has written past the end of the selection in the
42 /// original source text.
43 bool _passedSelectionEnd = false;
44
45 /// The character offset of the end of the selection, if there is a selection.
46 ///
47 /// This is calculated and cached by [_findSelectionEnd].
48 int _selectionEnd;
49
50 /// The rule that should be used for the contents of a literal body that are
51 /// about to be written.
52 ///
53 /// This is set by [visitArgumentList] to ensure that all block arguments
54 /// share a rule.
55 ///
56 /// If `null`, a literal body creates its own rule.
57 Rule _nextLiteralBodyRule;
58
59 /// A stack that tracks forcing nested collections to split.
60 ///
61 /// Each entry corresponds to a collection currently being visited and the
62 /// value is whether or not it should be forced to split. Every time a
63 /// collection is entered, it sets all of the existing elements to `true`
64 /// then it pushes `false` for itself.
65 ///
66 /// When done visiting the elements, it removes its value. If it was set to
67 /// `true`, we know we visited a nested collection so we force this one to
68 /// split.
69 final List<bool> _collectionSplits = [];
70
71 /// Initialize a newly created visitor to write source code representing
72 /// the visited nodes to the given [writer].
73 SourceVisitor(this._formatter, this._lineInfo, this._source) {
74 builder = new ChunkBuilder(_formatter, _source);
75 }
76
77 /// Runs the visitor on [node], formatting its contents.
78 ///
79 /// Returns a [SourceCode] containing the resulting formatted source and
80 /// updated selection, if any.
81 ///
82 /// This is the only method that should be called externally. Everything else
83 /// is effectively private.
84 SourceCode run(AstNode node) {
85 visit(node);
86
87 // Output trailing comments.
88 writePrecedingCommentsAndNewlines(node.endToken.next);
89
90 // Finish writing and return the complete result.
91 return builder.end();
92 }
93
94 visitAdjacentStrings(AdjacentStrings node) {
95 builder.startSpan();
96 builder.startRule();
97 visitNodes(node.strings, between: splitOrNewline);
98 builder.endRule();
99 builder.endSpan();
100 }
101
102 visitAnnotation(Annotation node) {
103 token(node.atSign);
104 visit(node.name);
105 token(node.period);
106 visit(node.constructorName);
107 visit(node.arguments);
108 }
109
110 /// Visits an argument list.
111 ///
112 /// This is a bit complex to handle the rules for formatting positional and
113 /// named arguments. The goals, in rough order of descending priority are:
114 ///
115 /// 1. Keep everything on the first line.
116 /// 2. Keep the named arguments together on the next line.
117 /// 3. Keep everything together on the second line.
118 /// 4. Split between one or more positional arguments, trying to keep as many
119 /// on earlier lines as possible.
120 /// 5. Split the named arguments each onto their own line.
121 visitArgumentList(ArgumentList node) {
122 // Corner case: handle empty argument lists.
123 if (node.arguments.isEmpty) {
124 token(node.leftParenthesis);
125
126 // If there is a comment inside the parens, do allow splitting before it.
127 if (node.rightParenthesis.precedingComments != null) soloZeroSplit();
128
129 token(node.rightParenthesis);
130 return;
131 }
132
133 new ArgumentListVisitor(this, node).visit();
134 }
135
136 visitAsExpression(AsExpression node) {
137 builder.startSpan();
138 visit(node.expression);
139 soloSplit();
140 token(node.asOperator);
141 space();
142 visit(node.type);
143 builder.endSpan();
144 }
145
146 visitAssertStatement(AssertStatement node) {
147 _simpleStatement(node, () {
148 token(node.assertKeyword);
149 token(node.leftParenthesis);
150 soloZeroSplit();
151 visit(node.condition);
152 token(node.rightParenthesis);
153 });
154 }
155
156 visitAssignmentExpression(AssignmentExpression node) {
157 builder.nestExpression();
158
159 visit(node.leftHandSide);
160 _visitAssignment(node.operator, node.rightHandSide);
161
162 builder.unnest();
163 }
164
165 visitAwaitExpression(AwaitExpression node) {
166 token(node.awaitKeyword);
167 space();
168 visit(node.expression);
169 }
170
171 visitBinaryExpression(BinaryExpression node) {
172 builder.startSpan();
173 builder.nestExpression();
174
175 // Start lazily so we don't force the operator to split if a line comment
176 // appears before the first operand.
177 builder.startLazyRule();
178
179 // Flatten out a tree/chain of the same precedence. If we split on this
180 // precedence level, we will break all of them.
181 var precedence = node.operator.type.precedence;
182
183 traverse(Expression e) {
184 if (e is BinaryExpression && e.operator.type.precedence == precedence) {
185 traverse(e.leftOperand);
186
187 space();
188 token(e.operator);
189
190 split();
191 traverse(e.rightOperand);
192 } else {
193 visit(e);
194 }
195 }
196
197 // Blocks as operands to infix operators should always nest like regular
198 // operands. (Granted, this case is exceedingly rare in real code.)
199 builder.startBlockArgumentNesting();
200
201 traverse(node);
202
203 builder.endBlockArgumentNesting();
204
205 builder.unnest();
206 builder.endSpan();
207 builder.endRule();
208 }
209
210 visitBlock(Block node) {
211 // For a block that is not a function body, just bump the indentation and
212 // keep it in the current block.
213 if (node.parent is! BlockFunctionBody) {
214 _writeBody(node.leftBracket, node.rightBracket, body: () {
215 visitNodes(node.statements, between: oneOrTwoNewlines, after: newline);
216 });
217 return;
218 }
219
220 _startLiteralBody(node.leftBracket);
221 visitNodes(node.statements, between: oneOrTwoNewlines, after: newline);
222 _endLiteralBody(node.rightBracket, forceSplit: node.statements.isNotEmpty);
223 }
224
225 visitBlockFunctionBody(BlockFunctionBody node) {
226 // Space after the parameter list.
227 space();
228
229 // The "async" or "sync" keyword.
230 token(node.keyword);
231
232 // The "*" in "async*" or "sync*".
233 token(node.star);
234 if (node.keyword != null) space();
235
236 visit(node.block);
237 }
238
239 visitBooleanLiteral(BooleanLiteral node) {
240 token(node.literal);
241 }
242
243 visitBreakStatement(BreakStatement node) {
244 _simpleStatement(node, () {
245 token(node.breakKeyword);
246 visit(node.label, before: space);
247 });
248 }
249
250 visitCascadeExpression(CascadeExpression node) {
251 // If the target of the cascade is a method call (or chain of them), we
252 // treat the nesting specially. Normally, you would end up with:
253 //
254 // receiver
255 // .method()
256 // .method()
257 // ..cascade()
258 // ..cascade();
259 //
260 // This is logical, since the method chain is an operand of the cascade
261 // expression, so it's more deeply nested. But it looks wrong, so we leave
262 // the method chain's nesting active until after the cascade sections to
263 // force the *cascades* to be deeper because it looks better:
264 //
265 // receiver
266 // .method()
267 // .method()
268 // ..cascade()
269 // ..cascade();
270 if (node.target is MethodInvocation) {
271 new CallChainVisitor(this, node.target).visit(unnest: false);
272 } else {
273 visit(node.target);
274 }
275
276 builder.nestExpression(indent: Indent.cascade, now: true);
277 builder.startBlockArgumentNesting();
278
279 // If the cascade sections have consistent names they can be broken
280 // normally otherwise they always get their own line.
281 if (_allowInlineCascade(node.cascadeSections)) {
282 builder.startRule();
283 zeroSplit();
284 visitNodes(node.cascadeSections, between: zeroSplit);
285 builder.endRule();
286 } else {
287 builder.startRule(new HardSplitRule());
288 zeroSplit();
289 visitNodes(node.cascadeSections, between: zeroSplit);
290 builder.endRule();
291 }
292
293 builder.endBlockArgumentNesting();
294 builder.unnest();
295
296 if (node.target is MethodInvocation) builder.unnest();
297 }
298
299 /// Whether a cascade should be allowed to be inline as opposed to one
300 /// expression per line.
301 bool _allowInlineCascade(List<Expression> sections) {
302 if (sections.length < 2) return true;
303
304 var name;
305 // We could be more forgiving about what constitutes sections with
306 // consistent names but for now we require all sections to have the same
307 // method name.
308 for (var expression in sections) {
309 if (expression is! MethodInvocation) return false;
310 if (name == null) {
311 name = expression.methodName.name;
312 } else if (name != expression.methodName.name) {
313 return false;
314 }
315 }
316 return true;
317 }
318
319 visitCatchClause(CatchClause node) {
320 token(node.onKeyword, after: space);
321 visit(node.exceptionType);
322
323 if (node.catchKeyword != null) {
324 if (node.exceptionType != null) {
325 space();
326 }
327 token(node.catchKeyword);
328 space();
329 token(node.leftParenthesis);
330 visit(node.exceptionParameter);
331 token(node.comma, after: space);
332 visit(node.stackTraceParameter);
333 token(node.rightParenthesis);
334 space();
335 } else {
336 space();
337 }
338 visit(node.body);
339 }
340
341 visitClassDeclaration(ClassDeclaration node) {
342 visitDeclarationMetadata(node.metadata);
343
344 builder.nestExpression();
345 modifier(node.abstractKeyword);
346 token(node.classKeyword);
347 space();
348 visit(node.name);
349 visit(node.typeParameters);
350 visit(node.extendsClause);
351
352 builder.startRule(new CombinatorRule());
353 visit(node.withClause);
354 visit(node.implementsClause);
355 builder.endRule();
356
357 visit(node.nativeClause, before: space);
358 space();
359
360 builder.unnest();
361 _writeBody(node.leftBracket, node.rightBracket, body: () {
362 if (node.members.isNotEmpty) {
363 for (var member in node.members) {
364 visit(member);
365
366 if (member == node.members.last) {
367 newline();
368 break;
369 }
370
371 var needsDouble = false;
372 if (member is ClassDeclaration) {
373 // Add a blank line after classes.
374 twoNewlines();
375 } else if (member is MethodDeclaration) {
376 // Add a blank line after non-empty block methods.
377 var method = member as MethodDeclaration;
378 if (method.body is BlockFunctionBody) {
379 var body = method.body as BlockFunctionBody;
380 needsDouble = body.block.statements.isNotEmpty;
381 }
382 }
383
384 if (needsDouble) {
385 twoNewlines();
386 } else {
387 // Variables and arrow-bodied members can be more tightly packed if
388 // the user wants to group things together.
389 oneOrTwoNewlines();
390 }
391 }
392 }
393 });
394 }
395
396 visitClassTypeAlias(ClassTypeAlias node) {
397 visitDeclarationMetadata(node.metadata);
398
399 _simpleStatement(node, () {
400 modifier(node.abstractKeyword);
401 token(node.typedefKeyword);
402 space();
403 visit(node.name);
404 visit(node.typeParameters);
405 space();
406 token(node.equals);
407 space();
408
409 visit(node.superclass);
410
411 builder.startRule(new CombinatorRule());
412 visit(node.withClause);
413 visit(node.implementsClause);
414 builder.endRule();
415 });
416 }
417
418 visitComment(Comment node) => null;
419
420 visitCommentReference(CommentReference node) => null;
421
422 visitCompilationUnit(CompilationUnit node) {
423 visit(node.scriptTag);
424
425 // Put a blank line between the library tag and the other directives.
426 var directives = node.directives;
427 if (directives.isNotEmpty && directives.first is LibraryDirective) {
428 visit(directives.first);
429 twoNewlines();
430
431 directives = directives.skip(1);
432 }
433
434 visitNodes(directives, between: oneOrTwoNewlines);
435
436 if (node.declarations.isNotEmpty) {
437 var needsDouble = true;
438
439 for (var declaration in node.declarations) {
440 // Add a blank line before classes.
441 if (declaration is ClassDeclaration) needsDouble = true;
442
443 if (needsDouble) {
444 twoNewlines();
445 } else {
446 // Variables and arrow-bodied members can be more tightly packed if
447 // the user wants to group things together.
448 oneOrTwoNewlines();
449 }
450
451 visit(declaration);
452
453 needsDouble = false;
454 if (declaration is ClassDeclaration) {
455 // Add a blank line after classes.
456 needsDouble = true;
457 } else if (declaration is FunctionDeclaration) {
458 // Add a blank line after non-empty block functions.
459 var function = declaration as FunctionDeclaration;
460 if (function.functionExpression.body is BlockFunctionBody) {
461 var body = function.functionExpression.body as BlockFunctionBody;
462 needsDouble = body.block.statements.isNotEmpty;
463 }
464 }
465 }
466 }
467 }
468
469 visitConditionalExpression(ConditionalExpression node) {
470 builder.nestExpression();
471
472 // Push any block arguments all the way past the leading "?" and ":".
473 builder.nestExpression(indent: Indent.block, now: true);
474 builder.startBlockArgumentNesting();
475 builder.unnest();
476
477 visit(node.condition);
478
479 builder.startSpan();
480
481 // If we split after one clause in a conditional, always split after both.
482 builder.startRule();
483 split();
484 token(node.question);
485 space();
486
487 builder.nestExpression();
488 visit(node.thenExpression);
489 builder.unnest();
490
491 split();
492 token(node.colon);
493 space();
494
495 visit(node.elseExpression);
496
497 builder.endRule();
498 builder.endSpan();
499 builder.endBlockArgumentNesting();
500 builder.unnest();
501 }
502
503 visitConstructorDeclaration(ConstructorDeclaration node) {
504 visitMemberMetadata(node.metadata);
505
506 modifier(node.externalKeyword);
507 modifier(node.constKeyword);
508 modifier(node.factoryKeyword);
509 visit(node.returnType);
510 token(node.period);
511 visit(node.name);
512
513 // Make the rule for the ":" span both the preceding parameter list and
514 // the entire initialization list. This ensures that we split before the
515 // ":" if the parameters and initialization list don't all fit on one line.
516 builder.startRule();
517
518 _visitBody(node.parameters, node.body, () {
519 // Check for redirects or initializer lists.
520 if (node.redirectedConstructor != null) {
521 _visitConstructorRedirects(node);
522 } else if (node.initializers.isNotEmpty) {
523 _visitConstructorInitializers(node);
524 }
525 });
526 }
527
528 void _visitConstructorRedirects(ConstructorDeclaration node) {
529 token(node.separator /* = */, before: space, after: space);
530 visitCommaSeparatedNodes(node.initializers);
531 visit(node.redirectedConstructor);
532 }
533
534 void _visitConstructorInitializers(ConstructorDeclaration node) {
535 // Shift the ":" forward.
536 builder.indent(Indent.constructorInitializer);
537
538 split();
539 token(node.separator); // ":".
540 space();
541
542 // Shift everything past the ":".
543 builder.indent();
544
545 for (var i = 0; i < node.initializers.length; i++) {
546 if (i > 0) {
547 // Preceding comma.
548 token(node.initializers[i].beginToken.previous);
549 newline();
550 }
551
552 node.initializers[i].accept(this);
553 }
554
555 builder.unindent();
556 builder.unindent();
557
558 // End the rule for ":" after all of the initializers.
559 builder.endRule();
560 }
561
562 visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
563 builder.nestExpression();
564
565 token(node.thisKeyword);
566 token(node.period);
567 visit(node.fieldName);
568
569 _visitAssignment(node.equals, node.expression);
570
571 builder.unnest();
572 }
573
574 visitConstructorName(ConstructorName node) {
575 visit(node.type);
576 token(node.period);
577 visit(node.name);
578 }
579
580 visitContinueStatement(ContinueStatement node) {
581 _simpleStatement(node, () {
582 token(node.continueKeyword);
583 visit(node.label, before: space);
584 });
585 }
586
587 visitDeclaredIdentifier(DeclaredIdentifier node) {
588 modifier(node.keyword);
589 visit(node.type, after: space);
590 visit(node.identifier);
591 }
592
593 visitDefaultFormalParameter(DefaultFormalParameter node) {
594 visit(node.parameter);
595 if (node.separator != null) {
596 builder.startSpan();
597 builder.nestExpression();
598
599 // The '=' separator is preceded by a space, ":" is not.
600 if (node.separator.type == TokenType.EQ) space();
601 token(node.separator);
602
603 soloSplit(Cost.assignment);
604 visit(node.defaultValue);
605
606 builder.unnest();
607 builder.endSpan();
608 }
609 }
610
611 visitDoStatement(DoStatement node) {
612 _simpleStatement(node, () {
613 token(node.doKeyword);
614 space();
615 visit(node.body);
616 space();
617 token(node.whileKeyword);
618 space();
619 token(node.leftParenthesis);
620 soloZeroSplit();
621 visit(node.condition);
622 token(node.rightParenthesis);
623 });
624 }
625
626 visitDoubleLiteral(DoubleLiteral node) {
627 token(node.literal);
628 }
629
630 visitEmptyFunctionBody(EmptyFunctionBody node) {
631 token(node.semicolon);
632 }
633
634 visitEmptyStatement(EmptyStatement node) {
635 token(node.semicolon);
636 }
637
638 visitEnumConstantDeclaration(EnumConstantDeclaration node) {
639 visit(node.name);
640 }
641
642 visitEnumDeclaration(EnumDeclaration node) {
643 visitDeclarationMetadata(node.metadata);
644
645 token(node.enumKeyword);
646 space();
647 visit(node.name);
648 space();
649
650 _writeBody(node.leftBracket, node.rightBracket, space: true, body: () {
651 visitCommaSeparatedNodes(node.constants, between: split);
652 });
653 }
654
655 visitExportDirective(ExportDirective node) {
656 visitDeclarationMetadata(node.metadata);
657
658 _simpleStatement(node, () {
659 token(node.keyword);
660 space();
661 visit(node.uri);
662
663 builder.startRule(new CombinatorRule());
664 visitNodes(node.combinators);
665 builder.endRule();
666 });
667 }
668
669 visitExpressionFunctionBody(ExpressionFunctionBody node) {
670 // Space after the parameter list.
671 space();
672
673 // The "async" or "sync" keyword.
674 token(node.keyword, after: space);
675
676 // Try to keep the "(...) => " with the start of the body for anonymous
677 // functions.
678 if (_isInLambda(node)) builder.startSpan();
679
680 token(node.functionDefinition); // "=>".
681
682 // Split after the "=>", using the rule created before the parameters
683 // by _visitBody().
684 split();
685 builder.endRule();
686
687 if (_isInLambda(node)) builder.endSpan();
688
689 builder.startBlockArgumentNesting();
690 builder.startSpan();
691 visit(node.expression);
692 builder.endSpan();
693 builder.endBlockArgumentNesting();
694
695 token(node.semicolon);
696 }
697
698 visitExpressionStatement(ExpressionStatement node) {
699 _simpleStatement(node, () {
700 visit(node.expression);
701 });
702 }
703
704 visitExtendsClause(ExtendsClause node) {
705 soloSplit();
706 token(node.extendsKeyword);
707 space();
708 visit(node.superclass);
709 }
710
711 visitFieldDeclaration(FieldDeclaration node) {
712 visitMemberMetadata(node.metadata);
713
714 _simpleStatement(node, () {
715 modifier(node.staticKeyword);
716 visit(node.fields);
717 });
718 }
719
720 visitFieldFormalParameter(FieldFormalParameter node) {
721 visitParameterMetadata(node.metadata, () {
722 token(node.keyword, after: space);
723 visit(node.type, after: space);
724 token(node.thisKeyword);
725 token(node.period);
726 visit(node.identifier);
727 visit(node.parameters);
728 });
729 }
730
731 visitForEachStatement(ForEachStatement node) {
732 builder.nestExpression();
733 token(node.awaitKeyword, after: space);
734 token(node.forKeyword);
735 space();
736 token(node.leftParenthesis);
737 if (node.loopVariable != null) {
738 visit(node.loopVariable);
739 } else {
740 visit(node.identifier);
741 }
742 soloSplit();
743 token(node.inKeyword);
744 space();
745 visit(node.iterable);
746 token(node.rightParenthesis);
747 space();
748 visit(node.body);
749 builder.unnest();
750 }
751
752 visitFormalParameterList(FormalParameterList node) {
753 // Corner case: empty parameter lists.
754 if (node.parameters.isEmpty) {
755 token(node.leftParenthesis);
756
757 // If there is a comment, do allow splitting before it.
758 if (node.rightParenthesis.precedingComments != null) soloZeroSplit();
759
760 token(node.rightParenthesis);
761 return;
762 }
763
764 var requiredParams = node.parameters
765 .where((param) => param is! DefaultFormalParameter)
766 .toList();
767 var optionalParams = node.parameters
768 .where((param) => param is DefaultFormalParameter)
769 .toList();
770
771 builder.nestExpression();
772 token(node.leftParenthesis);
773
774 var rule;
775 if (requiredParams.isNotEmpty) {
776 if (requiredParams.length > 1) {
777 rule = new MultiplePositionalRule(null, 0, 0);
778 } else {
779 rule = new SinglePositionalRule(null);
780 }
781
782 builder.startRule(rule);
783 if (_isInLambda(node)) {
784 // Don't allow splitting before the first argument (i.e. right after
785 // the bare "(" in a lambda. Instead, just stuff a null chunk in there
786 // to avoid confusing the arg rule.
787 rule.beforeArgument(null);
788 } else {
789 // Split before the first argument.
790 rule.beforeArgument(zeroSplit());
791 }
792
793 builder.startSpan();
794
795 for (var param in requiredParams) {
796 visit(param);
797
798 // Write the trailing comma.
799 if (param != node.parameters.last) token(param.endToken.next);
800
801 if (param != requiredParams.last) rule.beforeArgument(split());
802 }
803
804 builder.endSpan();
805 builder.endRule();
806 }
807
808 if (optionalParams.isNotEmpty) {
809 var namedRule = new NamedRule(null);
810 if (rule != null) rule.setNamedArgsRule(namedRule);
811
812 builder.startRule(namedRule);
813
814 namedRule
815 .beforeArguments(builder.split(space: requiredParams.isNotEmpty));
816
817 // "[" or "{" for optional parameters.
818 token(node.leftDelimiter);
819
820 for (var param in optionalParams) {
821 visit(param);
822
823 // Write the trailing comma.
824 if (param != node.parameters.last) token(param.endToken.next);
825 if (param != optionalParams.last) split();
826 }
827
828 builder.endRule();
829
830 // "]" or "}" for optional parameters.
831 token(node.rightDelimiter);
832 }
833
834 token(node.rightParenthesis);
835 builder.unnest();
836 }
837
838 visitForStatement(ForStatement node) {
839 builder.nestExpression();
840 token(node.forKeyword);
841 space();
842 token(node.leftParenthesis);
843
844 builder.startRule();
845
846 // The initialization clause.
847 if (node.initialization != null) {
848 visit(node.initialization);
849 } else if (node.variables != null) {
850 // Indent split variables more so they aren't at the same level
851 // as the rest of the loop clauses.
852 builder.indent(Indent.loopVariable);
853
854 // Allow the variables to stay unsplit even if the clauses split.
855 builder.startRule();
856
857 var declaration = node.variables;
858 visitDeclarationMetadata(declaration.metadata);
859 modifier(declaration.keyword);
860 visit(declaration.type, after: space);
861
862 visitCommaSeparatedNodes(declaration.variables, between: () {
863 split();
864 });
865
866 builder.endRule();
867 builder.unindent();
868 }
869
870 token(node.leftSeparator);
871
872 // The condition clause.
873 if (node.condition != null) split();
874 visit(node.condition);
875 token(node.rightSeparator);
876
877 // The update clause.
878 if (node.updaters.isNotEmpty) {
879 split();
880
881 // Allow the updates to stay unsplit even if the clauses split.
882 builder.startRule();
883
884 visitCommaSeparatedNodes(node.updaters, between: split);
885
886 builder.endRule();
887 }
888
889 token(node.rightParenthesis);
890 builder.endRule();
891 builder.unnest();
892
893 // The body.
894 if (node.body is! EmptyStatement) space();
895 visit(node.body);
896 }
897
898 visitFunctionDeclaration(FunctionDeclaration node) {
899 visitMemberMetadata(node.metadata);
900
901 builder.nestExpression();
902 modifier(node.externalKeyword);
903 visit(node.returnType, after: space);
904 modifier(node.propertyKeyword);
905 visit(node.name);
906 visit(node.functionExpression);
907 builder.unnest();
908 }
909
910 visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
911 visit(node.functionDeclaration);
912 }
913
914 visitFunctionExpression(FunctionExpression node) {
915 _visitBody(node.parameters, node.body);
916 }
917
918 visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
919 visit(node.function);
920 visit(node.argumentList);
921 }
922
923 visitFunctionTypeAlias(FunctionTypeAlias node) {
924 visitDeclarationMetadata(node.metadata);
925
926 _simpleStatement(node, () {
927 token(node.typedefKeyword);
928 space();
929 visit(node.returnType, after: space);
930 visit(node.name);
931 visit(node.typeParameters);
932 visit(node.parameters);
933 });
934 }
935
936 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
937 visitParameterMetadata(node.metadata, () {
938 visit(node.returnType, after: space);
939
940 // Try to keep the function's parameters with its name.
941 builder.startSpan();
942 visit(node.identifier);
943 visit(node.parameters);
944 builder.endSpan();
945 });
946 }
947
948 visitHideCombinator(HideCombinator node) {
949 _visitCombinator(node.keyword, node.hiddenNames);
950 }
951
952 visitIfStatement(IfStatement node) {
953 builder.nestExpression();
954 token(node.ifKeyword);
955 space();
956 token(node.leftParenthesis);
957 visit(node.condition);
958 token(node.rightParenthesis);
959
960 space();
961 visit(node.thenStatement);
962 builder.unnest();
963
964 if (node.elseStatement != null) {
965 if (node.thenStatement is Block) {
966 space();
967 } else {
968 // Corner case where an else follows a single-statement then clause.
969 // This is against the style guide, but we still need to handle it. If
970 // it happens, put the else on the next line.
971 newline();
972 }
973
974 token(node.elseKeyword);
975 space();
976 visit(node.elseStatement);
977 }
978 }
979
980 visitImplementsClause(ImplementsClause node) {
981 _visitCombinator(node.implementsKeyword, node.interfaces);
982 }
983
984 visitImportDirective(ImportDirective node) {
985 visitDeclarationMetadata(node.metadata);
986
987 _simpleStatement(node, () {
988 token(node.keyword);
989 space();
990 visit(node.uri);
991
992 if (node.asKeyword != null) {
993 soloSplit();
994 token(node.deferredKeyword, after: space);
995 token(node.asKeyword);
996 space();
997 visit(node.prefix);
998 }
999
1000 builder.startRule(new CombinatorRule());
1001 visitNodes(node.combinators);
1002 builder.endRule();
1003 });
1004 }
1005
1006 visitIndexExpression(IndexExpression node) {
1007 builder.nestExpression();
1008
1009 if (node.isCascaded) {
1010 token(node.period);
1011 } else {
1012 visit(node.target);
1013 }
1014
1015 if (node.target is IndexExpression) {
1016 // Corner case: On a chain of [] accesses, allow splitting between them.
1017 // Produces nicer output in cases like:
1018 //
1019 // someJson['property']['property']['property']['property']...
1020 soloZeroSplit();
1021 }
1022
1023 builder.startSpan();
1024 token(node.leftBracket);
1025 soloZeroSplit();
1026 visit(node.index);
1027 token(node.rightBracket);
1028 builder.endSpan();
1029 builder.unnest();
1030 }
1031
1032 visitInstanceCreationExpression(InstanceCreationExpression node) {
1033 builder.startSpan();
1034 token(node.keyword);
1035 space();
1036 visit(node.constructorName);
1037 visit(node.argumentList);
1038 builder.endSpan();
1039 }
1040
1041 visitIntegerLiteral(IntegerLiteral node) {
1042 token(node.literal);
1043 }
1044
1045 visitInterpolationExpression(InterpolationExpression node) {
1046 token(node.leftBracket);
1047 visit(node.expression);
1048 token(node.rightBracket);
1049 }
1050
1051 visitInterpolationString(InterpolationString node) {
1052 token(node.contents);
1053 }
1054
1055 visitIsExpression(IsExpression node) {
1056 builder.startSpan();
1057 visit(node.expression);
1058 soloSplit();
1059 token(node.isOperator);
1060 token(node.notOperator);
1061 space();
1062 visit(node.type);
1063 builder.endSpan();
1064 }
1065
1066 visitLabel(Label node) {
1067 visit(node.label);
1068 token(node.colon);
1069 }
1070
1071 visitLabeledStatement(LabeledStatement node) {
1072 visitNodes(node.labels, between: space, after: space);
1073 visit(node.statement);
1074 }
1075
1076 visitLibraryDirective(LibraryDirective node) {
1077 visitDeclarationMetadata(node.metadata);
1078
1079 _simpleStatement(node, () {
1080 token(node.keyword);
1081 space();
1082 visit(node.name);
1083 });
1084 }
1085
1086 visitLibraryIdentifier(LibraryIdentifier node) {
1087 visit(node.components.first);
1088 for (var component in node.components.skip(1)) {
1089 token(component.beginToken.previous); // "."
1090 visit(component);
1091 }
1092 }
1093
1094 visitListLiteral(ListLiteral node) {
1095 // Corner case: Splitting inside a list looks bad if there's only one
1096 // element, so make those more costly.
1097 var cost = node.elements.length <= 1 ? Cost.singleElementList : Cost.normal;
1098 _visitCollectionLiteral(
1099 node, node.leftBracket, node.elements, node.rightBracket, cost);
1100 }
1101
1102 visitMapLiteral(MapLiteral node) {
1103 _visitCollectionLiteral(
1104 node, node.leftBracket, node.entries, node.rightBracket);
1105 }
1106
1107 visitMapLiteralEntry(MapLiteralEntry node) {
1108 visit(node.key);
1109 token(node.separator);
1110 soloSplit();
1111 visit(node.value);
1112 }
1113
1114 visitMethodDeclaration(MethodDeclaration node) {
1115 visitMemberMetadata(node.metadata);
1116
1117 modifier(node.externalKeyword);
1118 modifier(node.modifierKeyword);
1119 visit(node.returnType, after: space);
1120 modifier(node.propertyKeyword);
1121 modifier(node.operatorKeyword);
1122 visit(node.name);
1123
1124 _visitBody(node.parameters, node.body);
1125 }
1126
1127 visitMethodInvocation(MethodInvocation node) {
1128 // If there's no target, this is a "bare" function call like "foo(1, 2)",
1129 // or a section in a cascade. Handle this case specially.
1130 if (node.target == null) {
1131 // Try to keep the entire method invocation one line.
1132 builder.startSpan();
1133 builder.nestExpression();
1134
1135 // This will be non-null for cascade sections.
1136 token(node.operator);
1137 token(node.methodName.token);
1138 visit(node.argumentList);
1139
1140 builder.unnest();
1141 builder.endSpan();
1142 return;
1143 }
1144
1145 new CallChainVisitor(this, node).visit();
1146 }
1147
1148 visitNamedExpression(NamedExpression node) {
1149 builder.nestExpression();
1150 builder.startSpan();
1151 visit(node.name);
1152 visit(node.expression, before: soloSplit);
1153 builder.endSpan();
1154 builder.unnest();
1155 }
1156
1157 visitNativeClause(NativeClause node) {
1158 token(node.nativeKeyword);
1159 space();
1160 visit(node.name);
1161 }
1162
1163 visitNativeFunctionBody(NativeFunctionBody node) {
1164 _simpleStatement(node, () {
1165 builder.nestExpression(now: true);
1166 soloSplit();
1167 token(node.nativeKeyword);
1168 space();
1169 visit(node.stringLiteral);
1170 builder.unnest();
1171 });
1172 }
1173
1174 visitNullLiteral(NullLiteral node) {
1175 token(node.literal);
1176 }
1177
1178 visitParenthesizedExpression(ParenthesizedExpression node) {
1179 builder.nestExpression();
1180 token(node.leftParenthesis);
1181 visit(node.expression);
1182 builder.unnest();
1183 token(node.rightParenthesis);
1184 }
1185
1186 visitPartDirective(PartDirective node) {
1187 _simpleStatement(node, () {
1188 token(node.keyword);
1189 space();
1190 visit(node.uri);
1191 });
1192 }
1193
1194 visitPartOfDirective(PartOfDirective node) {
1195 _simpleStatement(node, () {
1196 token(node.keyword);
1197 space();
1198 token(node.ofKeyword);
1199 space();
1200 visit(node.libraryName);
1201 });
1202 }
1203
1204 visitPostfixExpression(PostfixExpression node) {
1205 visit(node.operand);
1206 token(node.operator);
1207 }
1208
1209 visitPrefixedIdentifier(PrefixedIdentifier node) {
1210 visit(node.prefix);
1211 token(node.period);
1212 visit(node.identifier);
1213 }
1214
1215 visitPrefixExpression(PrefixExpression node) {
1216 token(node.operator);
1217
1218 // Corner case: put a space between successive "-" operators so we don't
1219 // inadvertently turn them into a "--" decrement operator.
1220 if (node.operand is PrefixExpression &&
1221 (node.operand as PrefixExpression).operator.lexeme == "-") {
1222 space();
1223 }
1224
1225 visit(node.operand);
1226 }
1227
1228 visitPropertyAccess(PropertyAccess node) {
1229 if (node.isCascaded) {
1230 token(node.operator);
1231 visit(node.propertyName);
1232 return;
1233 }
1234
1235 new CallChainVisitor(this, node).visit();
1236 }
1237
1238 visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
1239 builder.startSpan();
1240
1241 token(node.thisKeyword);
1242 token(node.period);
1243 visit(node.constructorName);
1244 visit(node.argumentList);
1245
1246 builder.endSpan();
1247 }
1248
1249 visitRethrowExpression(RethrowExpression node) {
1250 token(node.rethrowKeyword);
1251 }
1252
1253 visitReturnStatement(ReturnStatement node) {
1254 _simpleStatement(node, () {
1255 token(node.returnKeyword);
1256 visit(node.expression, before: space);
1257 });
1258 }
1259
1260 visitScriptTag(ScriptTag node) {
1261 // The lexeme includes the trailing newline. Strip it off since the
1262 // formatter ensures it gets a newline after it. Since the script tag must
1263 // come at the top of the file, we don't have to worry about preceding
1264 // comments or whitespace.
1265 _writeText(node.scriptTag.lexeme.trim(), node.offset);
1266
1267 oneOrTwoNewlines();
1268 }
1269
1270 visitShowCombinator(ShowCombinator node) {
1271 _visitCombinator(node.keyword, node.shownNames);
1272 }
1273
1274 visitSimpleFormalParameter(SimpleFormalParameter node) {
1275 visitParameterMetadata(node.metadata, () {
1276 modifier(node.keyword);
1277 visit(node.type, after: space);
1278 visit(node.identifier);
1279 });
1280 }
1281
1282 visitSimpleIdentifier(SimpleIdentifier node) {
1283 token(node.token);
1284 }
1285
1286 visitSimpleStringLiteral(SimpleStringLiteral node) {
1287 // Since we output the string literal manually, ensure any preceding
1288 // comments are written first.
1289 writePrecedingCommentsAndNewlines(node.literal);
1290
1291 _writeStringLiteral(node.literal.lexeme, node.offset);
1292 }
1293
1294 visitStringInterpolation(StringInterpolation node) {
1295 // Since we output the interpolated text manually, ensure we include any
1296 // preceding stuff first.
1297 writePrecedingCommentsAndNewlines(node.beginToken);
1298
1299 // Right now, the formatter does not try to do any reformatting of the
1300 // contents of interpolated strings. Instead, it treats the entire thing as
1301 // a single (possibly multi-line) chunk of text.
1302 _writeStringLiteral(
1303 _source.text.substring(node.beginToken.offset, node.endToken.end),
1304 node.offset);
1305 }
1306
1307 visitSuperConstructorInvocation(SuperConstructorInvocation node) {
1308 builder.startSpan();
1309
1310 token(node.superKeyword);
1311 token(node.period);
1312 visit(node.constructorName);
1313 visit(node.argumentList);
1314
1315 builder.endSpan();
1316 }
1317
1318 visitSuperExpression(SuperExpression node) {
1319 token(node.superKeyword);
1320 }
1321
1322 visitSwitchCase(SwitchCase node) {
1323 visitNodes(node.labels, between: space, after: space);
1324 token(node.keyword);
1325 space();
1326 visit(node.expression);
1327 token(node.colon);
1328
1329 builder.indent();
1330 // TODO(rnystrom): Allow inline cases?
1331 newline();
1332
1333 visitNodes(node.statements, between: oneOrTwoNewlines);
1334 builder.unindent();
1335 }
1336
1337 visitSwitchDefault(SwitchDefault node) {
1338 visitNodes(node.labels, between: space, after: space);
1339 token(node.keyword);
1340 token(node.colon);
1341
1342 builder.indent();
1343 // TODO(rnystrom): Allow inline cases?
1344 newline();
1345
1346 visitNodes(node.statements, between: oneOrTwoNewlines);
1347 builder.unindent();
1348 }
1349
1350 visitSwitchStatement(SwitchStatement node) {
1351 builder.nestExpression();
1352 token(node.switchKeyword);
1353 space();
1354 token(node.leftParenthesis);
1355 soloZeroSplit();
1356 visit(node.expression);
1357 token(node.rightParenthesis);
1358 space();
1359 token(node.leftBracket);
1360 builder.indent();
1361 newline();
1362
1363 visitNodes(node.members, between: oneOrTwoNewlines, after: newline);
1364 token(node.rightBracket, before: () {
1365 builder.unindent();
1366 newline();
1367 });
1368 builder.unnest();
1369 }
1370
1371 visitSymbolLiteral(SymbolLiteral node) {
1372 token(node.poundSign);
1373 var components = node.components;
1374 for (var component in components) {
1375 // The '.' separator
1376 if (component.previous.lexeme == '.') {
1377 token(component.previous);
1378 }
1379 token(component);
1380 }
1381 }
1382
1383 visitThisExpression(ThisExpression node) {
1384 token(node.thisKeyword);
1385 }
1386
1387 visitThrowExpression(ThrowExpression node) {
1388 token(node.throwKeyword);
1389 space();
1390 visit(node.expression);
1391 }
1392
1393 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
1394 visitDeclarationMetadata(node.metadata);
1395
1396 _simpleStatement(node, () {
1397 visit(node.variables);
1398 });
1399 }
1400
1401 visitTryStatement(TryStatement node) {
1402 token(node.tryKeyword);
1403 space();
1404 visit(node.body);
1405 visitNodes(node.catchClauses, before: space, between: space);
1406 token(node.finallyKeyword, before: space, after: space);
1407 visit(node.finallyBlock);
1408 }
1409
1410 visitTypeArgumentList(TypeArgumentList node) {
1411 _visitGenericList(node.leftBracket, node.rightBracket, node.arguments);
1412 }
1413
1414 visitTypeName(TypeName node) {
1415 visit(node.name);
1416 visit(node.typeArguments);
1417 }
1418
1419 visitTypeParameter(TypeParameter node) {
1420 visitParameterMetadata(node.metadata, () {
1421 visit(node.name);
1422 token(node.extendsKeyword, before: space, after: space);
1423 visit(node.bound);
1424 });
1425 }
1426
1427 visitTypeParameterList(TypeParameterList node) {
1428 _visitGenericList(node.leftBracket, node.rightBracket, node.typeParameters);
1429 }
1430
1431 visitVariableDeclaration(VariableDeclaration node) {
1432 visit(node.name);
1433 if (node.initializer == null) return;
1434
1435 _visitAssignment(node.equals, node.initializer);
1436 }
1437
1438 visitVariableDeclarationList(VariableDeclarationList node) {
1439 visitDeclarationMetadata(node.metadata);
1440 modifier(node.keyword);
1441 visit(node.type, after: space);
1442
1443 // Use a single rule for all of the variables. If there are multiple
1444 // declarations, we will try to keep them all on one line. If that isn't
1445 // possible, we split after *every* declaration so that each is on its own
1446 // line.
1447 builder.startRule();
1448 visitCommaSeparatedNodes(node.variables, between: split);
1449 builder.endRule();
1450 }
1451
1452 visitVariableDeclarationStatement(VariableDeclarationStatement node) {
1453 _simpleStatement(node, () {
1454 visit(node.variables);
1455 });
1456 }
1457
1458 visitWhileStatement(WhileStatement node) {
1459 builder.nestExpression();
1460 token(node.whileKeyword);
1461 space();
1462 token(node.leftParenthesis);
1463 soloZeroSplit();
1464 visit(node.condition);
1465 token(node.rightParenthesis);
1466 if (node.body is! EmptyStatement) space();
1467 visit(node.body);
1468 builder.unnest();
1469 }
1470
1471 visitWithClause(WithClause node) {
1472 _visitCombinator(node.withKeyword, node.mixinTypes);
1473 }
1474
1475 visitYieldStatement(YieldStatement node) {
1476 _simpleStatement(node, () {
1477 token(node.yieldKeyword);
1478 token(node.star);
1479 space();
1480 visit(node.expression);
1481 });
1482 }
1483
1484 /// Visit a [node], and if not null, optionally preceded or followed by the
1485 /// specified functions.
1486 void visit(AstNode node, {void before(), void after()}) {
1487 if (node == null) return;
1488
1489 if (before != null) before();
1490
1491 node.accept(this);
1492
1493 if (after != null) after();
1494 }
1495
1496 /// Visit metadata annotations on directives and declarations.
1497 ///
1498 /// These always force the annotations to be on the previous line.
1499 void visitDeclarationMetadata(NodeList<Annotation> metadata) {
1500 // If there are multiple annotations, they are always on their own lines,
1501 // even the last.
1502 if (metadata.length > 1) {
1503 visitNodes(metadata, between: newline, after: newline);
1504 } else {
1505 visitNodes(metadata, between: space, after: newline);
1506 }
1507 }
1508
1509 /// Visit metadata annotations on members.
1510 ///
1511 /// These may be on the same line as the member, or on the previous.
1512 void visitMemberMetadata(NodeList<Annotation> metadata) {
1513 // If there are multiple annotations, they are always on their own lines,
1514 // even the last.
1515 if (metadata.length > 1) {
1516 visitNodes(metadata, between: newline, after: newline);
1517 } else {
1518 visitNodes(metadata, between: space, after: spaceOrNewline);
1519 }
1520 }
1521
1522 /// Visits metadata annotations on parameters and type parameters.
1523 ///
1524 /// These are always on the same line as the parameter.
1525 void visitParameterMetadata(
1526 NodeList<Annotation> metadata, void visitParameter()) {
1527 // Split before all of the annotations or none.
1528 builder.startRule();
1529 visitNodes(metadata, between: split, after: split);
1530 visitParameter();
1531
1532 // Wrap the rule around the parameter too. If it splits, we want to force
1533 // the annotations to split as well.
1534 builder.endRule();
1535 }
1536
1537 /// Visits the `=` and the following expression in any place where an `=`
1538 /// appears:
1539 ///
1540 /// * Assignment
1541 /// * Variable declaration
1542 /// * Constructor initialization
1543 void _visitAssignment(Token equalsOperator, Expression rightHandSide) {
1544 space();
1545 token(equalsOperator);
1546 soloSplit(Cost.assignment);
1547 builder.startSpan();
1548 visit(rightHandSide);
1549 builder.endSpan();
1550 }
1551
1552 /// Visits a type parameter or type argument list.
1553 void _visitGenericList(
1554 Token leftBracket, Token rightBracket, List<AstNode> nodes) {
1555 var rule = new TypeArgumentRule();
1556 builder.startRule(rule);
1557 builder.startSpan();
1558 builder.nestExpression();
1559
1560 token(leftBracket);
1561 rule.beforeArgument(zeroSplit());
1562
1563 for (var node in nodes) {
1564 visit(node);
1565
1566 // Write the trailing comma.
1567 if (node != nodes.last) {
1568 token(node.endToken.next);
1569 rule.beforeArgument(split());
1570 }
1571 }
1572
1573 token(rightBracket);
1574
1575 builder.unnest();
1576 builder.endSpan();
1577 builder.endRule();
1578 }
1579
1580 /// Visit the given function [parameters] followed by its [body], printing a
1581 /// space before it if it's not empty.
1582 ///
1583 /// If [afterParameters] is provided, it is invoked between the parameters
1584 /// and body. (It's used for constructor initialization lists.)
1585 void _visitBody(FormalParameterList parameters, FunctionBody body,
1586 [afterParameters()]) {
1587 // If the body is "=>", add an extra level of indentation around the
1588 // parameters and a rule that spans the parameters and the "=>". This
1589 // ensures that if the parameters wrap, they wrap more deeply than the "=>"
1590 // does, as in:
1591 //
1592 // someFunction(parameter,
1593 // parameter, parameter) =>
1594 // "the body";
1595 //
1596 // Also, it ensures that if the parameters wrap, we split at the "=>" too
1597 // to avoid:
1598 //
1599 // someFunction(parameter,
1600 // parameter) => function(
1601 // argument);
1602 //
1603 // This is confusing because it looks like those two lines are at the same
1604 // level when they are actually unrelated. Splitting at "=>" forces:
1605 //
1606 // someFunction(parameter,
1607 // parameter) =>
1608 // function(
1609 // argument);
1610 if (body is ExpressionFunctionBody) {
1611 builder.nestExpression();
1612
1613 // This rule is ended by visitExpressionFunctionBody().
1614 builder.startLazyRule(new SimpleRule(cost: Cost.arrow));
1615 }
1616
1617 if (parameters != null) {
1618 builder.nestExpression();
1619
1620 visit(parameters);
1621 if (afterParameters != null) afterParameters();
1622
1623 builder.unnest();
1624 }
1625
1626 visit(body);
1627
1628 if (body is ExpressionFunctionBody) builder.unnest();
1629 }
1630
1631 /// Visit a list of [nodes] if not null, optionally separated and/or preceded
1632 /// and followed by the given functions.
1633 void visitNodes(Iterable<AstNode> nodes, {before(), between(), after()}) {
1634 if (nodes == null || nodes.isEmpty) return;
1635
1636 if (before != null) before();
1637
1638 visit(nodes.first);
1639 for (var node in nodes.skip(1)) {
1640 if (between != null) between();
1641 visit(node);
1642 }
1643
1644 if (after != null) after();
1645 }
1646
1647 /// Visit a comma-separated list of [nodes] if not null.
1648 void visitCommaSeparatedNodes(Iterable<AstNode> nodes, {between()}) {
1649 if (nodes == null || nodes.isEmpty) return;
1650
1651 if (between == null) between = space;
1652
1653 var first = true;
1654 for (var node in nodes) {
1655 if (!first) between();
1656 first = false;
1657
1658 visit(node);
1659
1660 // The comma after the node.
1661 if (node.endToken.next.lexeme == ",") token(node.endToken.next);
1662 }
1663 }
1664
1665 /// Visits the collection literal [node] whose body starts with [leftBracket],
1666 /// ends with [rightBracket] and contains [elements].
1667 void _visitCollectionLiteral(TypedLiteral node, Token leftBracket,
1668 Iterable<AstNode> elements, Token rightBracket,
1669 [int cost]) {
1670 modifier(node.constKeyword);
1671 visit(node.typeArguments);
1672
1673 // Don't allow splitting in an empty collection.
1674 if (elements.isEmpty && rightBracket.precedingComments == null) {
1675 token(leftBracket);
1676 token(rightBracket);
1677
1678 // Clear this out in case this empty collection is in an argument list.
1679 // We don't want this rule to bleed over to some other collection.
1680 _nextLiteralBodyRule = null;
1681 return;
1682 }
1683
1684 // Force all of the surrounding collections to split.
1685 for (var i = 0; i < _collectionSplits.length; i++) {
1686 _collectionSplits[i] = true;
1687 }
1688
1689 // Add this collection to the stack.
1690 _collectionSplits.add(false);
1691
1692 _startLiteralBody(leftBracket);
1693
1694 // Always use a hard rule to split the elements. The parent chunk of
1695 // the collection will handle the unsplit case, so this only comes
1696 // into play when the collection is split.
1697 var rule = new HardSplitRule();
1698 builder.startRule(rule);
1699
1700 // If a collection contains a line comment, we assume it's a big complex
1701 // blob of data with some documented structure. In that case, the user
1702 // probably broke the elements into lines deliberately, so preserve those.
1703 var preserveNewlines = _containsLineComments(elements, rightBracket);
1704
1705 for (var element in elements) {
1706 if (element != elements.first) {
1707 if (preserveNewlines) {
1708 if (_endLine(element.beginToken.previous) !=
1709 _startLine(element.beginToken)) {
1710 oneOrTwoNewlines();
1711 } else {
1712 soloSplit();
1713 }
1714 } else {
1715 builder.blockSplit(space: true);
1716 }
1717 }
1718
1719 builder.nestExpression();
1720 visit(element);
1721
1722 // The comma after the element.
1723 if (element.endToken.next.lexeme == ",") token(element.endToken.next);
1724
1725 builder.unnest();
1726 }
1727
1728 builder.endRule();
1729
1730 // If there is a collection inside this one, it forces this one to split.
1731 var force = _collectionSplits.removeLast();
1732
1733 _endLiteralBody(rightBracket, ignoredRule: rule, forceSplit: force);
1734 }
1735
1736 /// Returns `true` if the collection withs [elements] delimited by
1737 /// [rightBracket] contains any line comments.
1738 ///
1739 /// This only looks for comments at the element boundary. Comments within an
1740 /// element are ignored.
1741 bool _containsLineComments(Iterable<AstNode> elements, Token rightBracket) {
1742 hasLineCommentBefore(token) {
1743 var comment = token.precedingComments;
1744 for (; comment != null; comment = comment.next) {
1745 if (comment.type == TokenType.SINGLE_LINE_COMMENT) return true;
1746 }
1747
1748 return false;
1749 }
1750
1751 // Look before each element.
1752 for (var element in elements) {
1753 if (hasLineCommentBefore(element.beginToken)) return true;
1754 }
1755
1756 // Look before the closing bracket.
1757 return hasLineCommentBefore(rightBracket);
1758 }
1759
1760 /// Begins writing a literal body: a collection or block-bodied function
1761 /// expression.
1762 ///
1763 /// Writes the delimiter and then creates the [Rule] that handles splitting
1764 /// the body.
1765 void _startLiteralBody(Token leftBracket) {
1766 token(leftBracket);
1767
1768 // Split the literal. Use the explicitly given rule if we have one.
1769 // Otherwise, create a new rule.
1770 var rule = _nextLiteralBodyRule;
1771 _nextLiteralBodyRule = null;
1772
1773 // Create a rule for whether or not to split the block contents.
1774 builder.startRule(rule);
1775
1776 // Process the collection contents as a separate set of chunks.
1777 builder = builder.startBlock();
1778 }
1779
1780 /// Ends the literal body started by a call to [_startLiteralBody()].
1781 ///
1782 /// If [forceSplit] is `true`, forces the body to split. If [ignoredRule] is
1783 /// given, ignores that rule inside the body when determining if it should
1784 /// split.
1785 void _endLiteralBody(Token rightBracket,
1786 {Rule ignoredRule, bool forceSplit}) {
1787 if (forceSplit == null) forceSplit = false;
1788
1789 // Put comments before the closing delimiter inside the block.
1790 var hasLeadingNewline = writePrecedingCommentsAndNewlines(rightBracket);
1791
1792 builder = builder.endBlock(ignoredRule,
1793 forceSplit: hasLeadingNewline || forceSplit);
1794
1795 builder.endRule();
1796
1797 // Now write the delimiter itself.
1798 _writeText(rightBracket.lexeme, rightBracket.offset);
1799 }
1800
1801 /// Visits a "combinator".
1802 ///
1803 /// This is a [keyword] followed by a list of [nodes], with specific line
1804 /// splitting rules. As the name implies, this is used for [HideCombinator]
1805 /// and [ShowCombinator], but it also used for "with" and "implements"
1806 /// clauses in class declarations, which are formatted the same way.
1807 ///
1808 /// This assumes the current rule is a [CombinatorRule].
1809 void _visitCombinator(Token keyword, Iterable<AstNode> nodes) {
1810 // Allow splitting before the keyword.
1811 var rule = builder.rule as CombinatorRule;
1812 rule.addCombinator(split());
1813
1814 builder.nestExpression();
1815 token(keyword);
1816
1817 rule.addName(split());
1818 visitCommaSeparatedNodes(nodes, between: () => rule.addName(split()));
1819
1820 builder.unnest();
1821 }
1822
1823 /// Writes the simple statement or semicolon-delimited top-level declaration.
1824 ///
1825 /// Handles nesting if a line break occurs in the statement and writes the
1826 /// terminating semicolon. Invokes [body] which should write statement itself.
1827 void _simpleStatement(AstNode node, body()) {
1828 builder.nestExpression();
1829 body();
1830
1831 // TODO(rnystrom): Can the analyzer move "semicolon" to some shared base
1832 // type?
1833 token((node as dynamic).semicolon);
1834 builder.unnest();
1835 }
1836
1837 /// Makes [rule] the rule that will be used for the contents of a collection
1838 /// or function literal body that are about to be visited.
1839 void setNextLiteralBodyRule(Rule rule) {
1840 _nextLiteralBodyRule = rule;
1841 }
1842
1843 /// Writes an bracket-delimited body and handles indenting and starting the
1844 /// rule used to split the contents.
1845 ///
1846 /// If [space] is `true`, then the contents and delimiters will have a space
1847 /// between then when unsplit.
1848 void _writeBody(Token leftBracket, Token rightBracket,
1849 {bool space: false, body()}) {
1850 token(leftBracket);
1851
1852 // Indent the body.
1853 builder.indent();
1854
1855 // Split after the bracket.
1856 builder.startRule();
1857 builder.blockSplit(space: space, isDouble: false);
1858
1859 body();
1860
1861 token(rightBracket, before: () {
1862 // Split before the closing bracket character.
1863 builder.unindent();
1864 builder.blockSplit(space: space);
1865 });
1866
1867 builder.endRule();
1868 }
1869
1870 /// Returns `true` if [node] is immediately contained within an anonymous
1871 /// [FunctionExpression].
1872 bool _isInLambda(AstNode node) => node.parent is FunctionExpression &&
1873 node.parent.parent is! FunctionDeclaration;
1874
1875 /// Writes the string literal [string] to the output.
1876 ///
1877 /// Splits multiline strings into separate chunks so that the line splitter
1878 /// can handle them correctly.
1879 void _writeStringLiteral(String string, int offset) {
1880 // Split each line of a multiline string into separate chunks.
1881 var lines = string.split(_formatter.lineEnding);
1882
1883 _writeText(lines.first, offset);
1884 offset += lines.first.length;
1885
1886 for (var line in lines.skip(1)) {
1887 builder.writeWhitespace(Whitespace.newlineFlushLeft);
1888 offset++;
1889 _writeText(line, offset);
1890 offset += line.length;
1891 }
1892 }
1893
1894 /// Emit the given [modifier] if it's non null, followed by non-breaking
1895 /// whitespace.
1896 void modifier(Token modifier) {
1897 token(modifier, after: space);
1898 }
1899
1900 /// Emit a non-breaking space.
1901 void space() {
1902 builder.writeWhitespace(Whitespace.space);
1903 }
1904
1905 /// Emit a single mandatory newline.
1906 void newline() {
1907 builder.writeWhitespace(Whitespace.newline);
1908 }
1909
1910 /// Emit a two mandatory newlines.
1911 void twoNewlines() {
1912 builder.writeWhitespace(Whitespace.twoNewlines);
1913 }
1914
1915 /// Allow either a single space or newline to be emitted before the next
1916 /// non-whitespace token based on whether a newline exists in the source
1917 /// between the last token and the next one.
1918 void spaceOrNewline() {
1919 builder.writeWhitespace(Whitespace.spaceOrNewline);
1920 }
1921
1922 /// Allow either a single split or newline to be emitted before the next
1923 /// non-whitespace token based on whether a newline exists in the source
1924 /// between the last token and the next one.
1925 void splitOrNewline() {
1926 builder.writeWhitespace(Whitespace.splitOrNewline);
1927 }
1928
1929 /// Allow either one or two newlines to be emitted before the next
1930 /// non-whitespace token based on whether more than one newline exists in the
1931 /// source between the last token and the next one.
1932 void oneOrTwoNewlines() {
1933 builder.writeWhitespace(Whitespace.oneOrTwoNewlines);
1934 }
1935
1936 /// Writes a single space split owned by the current rule.
1937 ///
1938 /// Returns the chunk the split was applied to.
1939 Chunk split() => builder.split(space: true);
1940
1941 /// Writes a zero-space split owned by the current rule.
1942 ///
1943 /// Returns the chunk the split was applied to.
1944 Chunk zeroSplit() => builder.split();
1945
1946 /// Writes a single space split with its own rule.
1947 void soloSplit([int cost]) {
1948 builder.startRule(new SimpleRule(cost: cost));
1949 split();
1950 builder.endRule();
1951 }
1952
1953 /// Writes a zero-space split with its own rule.
1954 void soloZeroSplit() {
1955 builder.startRule();
1956 builder.split();
1957 builder.endRule();
1958 }
1959
1960 /// Emit [token], along with any comments and formatted whitespace that comes
1961 /// before it.
1962 ///
1963 /// Does nothing if [token] is `null`. If [before] is given, it will be
1964 /// executed before the token is outout. Likewise, [after] will be called
1965 /// after the token is output.
1966 void token(Token token, {before(), after()}) {
1967 if (token == null) return;
1968
1969 writePrecedingCommentsAndNewlines(token);
1970
1971 if (before != null) before();
1972
1973 _writeText(token.lexeme, token.offset);
1974
1975 if (after != null) after();
1976 }
1977
1978 /// Writes all formatted whitespace and comments that appear before [token].
1979 bool writePrecedingCommentsAndNewlines(Token token) {
1980 var comment = token.precedingComments;
1981
1982 // For performance, avoid calculating newlines between tokens unless
1983 // actually needed.
1984 if (comment == null) {
1985 if (builder.needsToPreserveNewlines) {
1986 builder.preserveNewlines(_startLine(token) - _endLine(token.previous));
1987 }
1988
1989 return false;
1990 }
1991
1992 var previousLine = _endLine(token.previous);
1993
1994 // Corner case! The analyzer includes the "\n" in the script tag's lexeme,
1995 // which means it appears to be one line later than it is. That causes a
1996 // comment following it to appear to be on the same line. Fix that here by
1997 // correcting the script tag's line.
1998 if (token.previous.type == TokenType.SCRIPT_TAG) previousLine--;
1999
2000 var tokenLine = _startLine(token);
2001
2002 var comments = [];
2003 while (comment != null) {
2004 var commentLine = _startLine(comment);
2005
2006 // Don't preserve newlines at the top of the file.
2007 if (comment == token.precedingComments &&
2008 token.previous.type == TokenType.EOF) {
2009 previousLine = commentLine;
2010 }
2011
2012 var text = comment.toString().trim();
2013 var linesBefore = commentLine - previousLine;
2014 var flushLeft = _startColumn(comment) == 1;
2015
2016 if (text.startsWith("///") && !text.startsWith("////")) {
2017 // Line doc comments are always indented even if they were flush left.
2018 flushLeft = false;
2019
2020 // Always add a blank line (if possible) before a doc comment block.
2021 if (comment == token.precedingComments) linesBefore = 2;
2022 }
2023
2024 var sourceComment = new SourceComment(text, linesBefore,
2025 isLineComment: comment.type == TokenType.SINGLE_LINE_COMMENT,
2026 flushLeft: flushLeft);
2027
2028 // If this comment contains either of the selection endpoints, mark them
2029 // in the comment.
2030 var start = _getSelectionStartWithin(comment.offset, comment.length);
2031 if (start != null) sourceComment.startSelection(start);
2032
2033 var end = _getSelectionEndWithin(comment.offset, comment.length);
2034 if (end != null) sourceComment.endSelection(end);
2035
2036 comments.add(sourceComment);
2037
2038 previousLine = _endLine(comment);
2039 comment = comment.next;
2040 }
2041
2042 builder.writeComments(comments, tokenLine - previousLine, token.lexeme);
2043
2044 // TODO(rnystrom): This is wrong. Consider:
2045 //
2046 // [/* inline comment */
2047 // // line comment
2048 // element];
2049 return comments.first.linesBefore > 0;
2050 }
2051
2052 /// Write [text] to the current chunk, given that it starts at [offset] in
2053 /// the original source.
2054 ///
2055 /// Also outputs the selection endpoints if needed.
2056 void _writeText(String text, int offset) {
2057 builder.write(text);
2058
2059 // If this text contains either of the selection endpoints, mark them in
2060 // the chunk.
2061 var start = _getSelectionStartWithin(offset, text.length);
2062 if (start != null) {
2063 builder.startSelectionFromEnd(text.length - start);
2064 }
2065
2066 var end = _getSelectionEndWithin(offset, text.length);
2067 if (end != null) {
2068 builder.endSelectionFromEnd(text.length - end);
2069 }
2070 }
2071
2072 /// Returns the number of characters past [offset] in the source where the
2073 /// selection start appears if it appears before `offset + length`.
2074 ///
2075 /// Returns `null` if the selection start has already been processed or is
2076 /// not within that range.
2077 int _getSelectionStartWithin(int offset, int length) {
2078 // If there is no selection, do nothing.
2079 if (_source.selectionStart == null) return null;
2080
2081 // If we've already passed it, don't consider it again.
2082 if (_passedSelectionStart) return null;
2083
2084 var start = _source.selectionStart - offset;
2085
2086 // If it started in whitespace before this text, push it forward to the
2087 // beginning of the non-whitespace text.
2088 if (start < 0) start = 0;
2089
2090 // If we haven't reached it yet, don't consider it.
2091 if (start >= length) return null;
2092
2093 // We found it.
2094 _passedSelectionStart = true;
2095
2096 return start;
2097 }
2098
2099 /// Returns the number of characters past [offset] in the source where the
2100 /// selection endpoint appears if it appears before `offset + length`.
2101 ///
2102 /// Returns `null` if the selection endpoint has already been processed or is
2103 /// not within that range.
2104 int _getSelectionEndWithin(int offset, int length) {
2105 // If there is no selection, do nothing.
2106 if (_source.selectionLength == null) return null;
2107
2108 // If we've already passed it, don't consider it again.
2109 if (_passedSelectionEnd) return null;
2110
2111 var end = _findSelectionEnd() - offset;
2112
2113 // If it started in whitespace before this text, push it forward to the
2114 // beginning of the non-whitespace text.
2115 if (end < 0) end = 0;
2116
2117 // If we haven't reached it yet, don't consider it.
2118 if (end > length) return null;
2119
2120 if (end == length && _findSelectionEnd() == _source.selectionStart) {
2121 return null;
2122 }
2123
2124 // We found it.
2125 _passedSelectionEnd = true;
2126
2127 return end;
2128 }
2129
2130 /// Calculates the character offset in the source text of the end of the
2131 /// selection.
2132 ///
2133 /// Removes any trailing whitespace from the selection.
2134 int _findSelectionEnd() {
2135 if (_selectionEnd != null) return _selectionEnd;
2136
2137 _selectionEnd = _source.selectionStart + _source.selectionLength;
2138
2139 // If the selection bumps to the end of the source, pin it there.
2140 if (_selectionEnd == _source.text.length) return _selectionEnd;
2141
2142 // Trim off any trailing whitespace. We want the selection to "rubberband"
2143 // around the selected non-whitespace tokens since the whitespace will
2144 // be munged by the formatter itself.
2145 while (_selectionEnd > _source.selectionStart) {
2146 // Stop if we hit anything other than space, tab, newline or carriage
2147 // return.
2148 var char = _source.text.codeUnitAt(_selectionEnd - 1);
2149 if (char != 0x20 && char != 0x09 && char != 0x0a && char != 0x0d) {
2150 break;
2151 }
2152
2153 _selectionEnd--;
2154 }
2155
2156 return _selectionEnd;
2157 }
2158
2159 /// Gets the 1-based line number that the beginning of [token] lies on.
2160 int _startLine(Token token) => _lineInfo.getLocation(token.offset).lineNumber;
2161
2162 /// Gets the 1-based line number that the end of [token] lies on.
2163 int _endLine(Token token) => _lineInfo.getLocation(token.end).lineNumber;
2164
2165 /// Gets the 1-based column number that the beginning of [token] lies on.
2166 int _startColumn(Token token) =>
2167 _lineInfo.getLocation(token.offset).columnNumber;
2168 }
OLDNEW
« no previous file with comments | « dart_style/lib/src/source_code.dart ('k') | dart_style/lib/src/whitespace.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698