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

Side by Side Diff: observatory_pub_packages/analyzer/src/services/formatter_impl.dart

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, 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 formatter_impl;
6
7 import 'dart:math';
8
9 import 'package:analyzer/analyzer.dart';
10 import 'package:analyzer/src/generated/parser.dart';
11 import 'package:analyzer/src/generated/scanner.dart';
12 import 'package:analyzer/src/generated/source.dart';
13 import 'package:analyzer/src/services/writer.dart';
14
15 /// Formatter options.
16 class FormatterOptions {
17
18 /// Create formatter options with defaults derived (where defined) from
19 /// the style guide: <http://www.dartlang.org/articles/style-guide/>.
20 const FormatterOptions({this.initialIndentationLevel: 0,
21 this.spacesPerIndent: 2,
22 this.lineSeparator: NEW_LINE,
23 this.pageWidth: 80,
24 this.tabsForIndent: false,
25 this.tabSize: 2,
26 this.codeTransforms: false});
27
28 final String lineSeparator;
29 final int initialIndentationLevel;
30 final int spacesPerIndent;
31 final int tabSize;
32 final bool tabsForIndent;
33 final int pageWidth;
34 final bool codeTransforms;
35 }
36
37
38 /// Thrown when an error occurs in formatting.
39 class FormatterException implements Exception {
40
41 /// A message describing the error.
42 final String message;
43
44 /// Creates a new FormatterException with an optional error [message].
45 const FormatterException([this.message = 'FormatterException']);
46
47 FormatterException.forError(List<AnalysisError> errors, [LineInfo line]) :
48 message = _createMessage(errors);
49
50 static String _createMessage(errors) {
51 //TODO(pquitslund): consider a verbosity flag to add/suppress details
52 var errorCode = errors[0].errorCode;
53 var phase = errorCode is ParserErrorCode ? 'parsing' : 'scanning';
54 return 'An error occured while ${phase} (${errorCode.name}).';
55 }
56
57 String toString() => '$message';
58 }
59
60 /// Specifies the kind of code snippet to format.
61 class CodeKind {
62
63 final int _index;
64
65 const CodeKind._(this._index);
66
67 /// A compilation unit snippet.
68 static const COMPILATION_UNIT = const CodeKind._(0);
69
70 /// A statement snippet.
71 static const STATEMENT = const CodeKind._(1);
72
73 }
74
75 /// Dart source code formatter.
76 abstract class CodeFormatter {
77
78 factory CodeFormatter([FormatterOptions options = const FormatterOptions()])
79 => new CodeFormatterImpl(options);
80
81 /// Format the specified portion (from [offset] with [length]) of the given
82 /// [source] string, optionally providing an [indentationLevel].
83 FormattedSource format(CodeKind kind, String source, {int offset, int end,
84 int indentationLevel: 0, Selection selection: null});
85
86 }
87
88 /// Source selection state information.
89 class Selection {
90
91 /// The offset of the source selection.
92 final int offset;
93
94 /// The length of the selection.
95 final int length;
96
97 Selection(this.offset, this.length);
98
99 String toString() => 'Selection (offset: $offset, length: $length)';
100 }
101
102 /// Formatted source.
103 class FormattedSource {
104
105 /// Selection state or null if unspecified.
106 Selection selection;
107
108 /// Formatted source string.
109 final String source;
110
111 /// Create a formatted [source] result, with optional [selection] information.
112 FormattedSource(this.source, [this.selection = null]);
113 }
114
115
116 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener {
117
118 final FormatterOptions options;
119 final errors = <AnalysisError>[];
120 final whitespace = new RegExp(r'[\s]+');
121
122 LineInfo lineInfo;
123
124 CodeFormatterImpl(this.options);
125
126 FormattedSource format(CodeKind kind, String source, {int offset, int end,
127 int indentationLevel: 0, Selection selection: null}) {
128
129 var startToken = tokenize(source);
130 checkForErrors();
131
132 var node = parse(kind, startToken);
133 checkForErrors();
134
135 var formatter = new SourceVisitor(options, lineInfo, source, selection);
136 node.accept(formatter);
137
138 var formattedSource = formatter.writer.toString();
139
140 checkTokenStreams(startToken, tokenize(formattedSource),
141 allowTransforms: options.codeTransforms);
142
143 return new FormattedSource(formattedSource, formatter.selection);
144 }
145
146 checkTokenStreams(Token t1, Token t2, {allowTransforms: false}) =>
147 new TokenStreamComparator(lineInfo, t1, t2, transforms: allowTransforms).
148 verifyEquals();
149
150 AstNode parse(CodeKind kind, Token start) {
151
152 var parser = new Parser(null, this);
153
154 switch (kind) {
155 case CodeKind.COMPILATION_UNIT:
156 return parser.parseCompilationUnit(start);
157 case CodeKind.STATEMENT:
158 return parser.parseStatement(start);
159 }
160
161 throw new FormatterException('Unsupported format kind: $kind');
162 }
163
164 checkForErrors() {
165 if (errors.length > 0) {
166 throw new FormatterException.forError(errors);
167 }
168 }
169
170 onError(AnalysisError error) {
171 errors.add(error);
172 }
173
174 Token tokenize(String source) {
175 var reader = new CharSequenceReader(source);
176 var scanner = new Scanner(null, reader, this);
177 var token = scanner.tokenize();
178 lineInfo = new LineInfo(scanner.lineStarts);
179 return token;
180 }
181
182 }
183
184
185 // Compares two token streams. Used for sanity checking formatted results.
186 class TokenStreamComparator {
187
188 final LineInfo lineInfo;
189 Token token1, token2;
190 bool allowTransforms;
191
192 TokenStreamComparator(this.lineInfo, this.token1, this.token2,
193 {transforms: false}) : this.allowTransforms = transforms;
194
195 /// Verify that these two token streams are equal.
196 verifyEquals() {
197 while (!isEOF(token1)) {
198 checkPrecedingComments();
199 if (!checkTokens()) {
200 throwNotEqualException(token1, token2);
201 }
202 advance();
203
204 }
205 // TODO(pquitslund): consider a better way to notice trailing synthetics
206 if (!isEOF(token2) &&
207 !(isCLOSE_CURLY_BRACKET(token2) && isEOF(token2.next))) {
208 throw new FormatterException(
209 'Expected "EOF" but got "${token2}".');
210 }
211 }
212
213 checkPrecedingComments() {
214 var comment1 = token1.precedingComments;
215 var comment2 = token2.precedingComments;
216 while (comment1 != null) {
217 if (comment2 == null) {
218 throw new FormatterException(
219 'Expected comment, "${comment1}", at ${describeLocation(token1)}, '
220 'but got none.');
221 }
222 if (!equivalentComments(comment1, comment2)) {
223 throwNotEqualException(comment1, comment2);
224 }
225 comment1 = comment1.next;
226 comment2 = comment2.next;
227 }
228 if (comment2 != null) {
229 throw new FormatterException(
230 'Unexpected comment, "${comment2}", at ${describeLocation(token2)}.');
231 }
232 }
233
234 bool equivalentComments(Token comment1, Token comment2) =>
235 comment1.lexeme.trim() == comment2.lexeme.trim();
236
237 throwNotEqualException(t1, t2) {
238 throw new FormatterException(
239 'Expected "${t1}" but got "${t2}", at ${describeLocation(t1)}.');
240 }
241
242 String describeLocation(Token token) => lineInfo == null ? '<unknown>' :
243 'Line: ${lineInfo.getLocation(token.offset).lineNumber}, '
244 'Column: ${lineInfo.getLocation(token.offset).columnNumber}';
245
246 advance() {
247 token1 = token1.next;
248 token2 = token2.next;
249 }
250
251 bool checkTokens() {
252 if (token1 == null || token2 == null) {
253 return false;
254 }
255 if (token1 == token2 || token1.lexeme == token2.lexeme) {
256 return true;
257 }
258
259 // '[' ']' => '[]'
260 if (isOPEN_SQ_BRACKET(token1) && isCLOSE_SQUARE_BRACKET(token1.next)) {
261 if (isINDEX(token2)) {
262 token1 = token1.next;
263 return true;
264 }
265 }
266 // '>' '>' => '>>'
267 if (isGT(token1) && isGT(token1.next)) {
268 if (isGT_GT(token2)) {
269 token1 = token1.next;
270 return true;
271 }
272 }
273 // Cons(){} => Cons();
274 if (isOPEN_CURLY_BRACKET(token1) && isCLOSE_CURLY_BRACKET(token1.next)) {
275 if (isSEMICOLON(token2)) {
276 token1 = token1.next;
277 advance();
278 return true;
279 }
280 }
281
282 // Transform-related special casing
283 if (allowTransforms) {
284
285 // Advance past empty statements
286 if (isSEMICOLON(token1)) {
287 // TODO whitelist
288 token1 = token1.next;
289 return checkTokens();
290 }
291
292 // Advance past synthetic { } tokens
293 if (isOPEN_CURLY_BRACKET(token2) || isCLOSE_CURLY_BRACKET(token2)) {
294 token2 = token2.next;
295 return checkTokens();
296 }
297
298 }
299
300 return false;
301 }
302
303 }
304
305 /// Test for token type.
306 bool tokenIs(Token token, TokenType type) =>
307 token != null && token.type == type;
308
309 /// Test if this token is an EOF token.
310 bool isEOF(Token token) => tokenIs(token, TokenType.EOF);
311
312 /// Test if this token is a GT token.
313 bool isGT(Token token) => tokenIs(token, TokenType.GT);
314
315 /// Test if this token is a GT_GT token.
316 bool isGT_GT(Token token) => tokenIs(token, TokenType.GT_GT);
317
318 /// Test if this token is an INDEX token.
319 bool isINDEX(Token token) => tokenIs(token, TokenType.INDEX);
320
321 /// Test if this token is a OPEN_CURLY_BRACKET token.
322 bool isOPEN_CURLY_BRACKET(Token token) =>
323 tokenIs(token, TokenType.OPEN_CURLY_BRACKET);
324
325 /// Test if this token is a CLOSE_CURLY_BRACKET token.
326 bool isCLOSE_CURLY_BRACKET(Token token) =>
327 tokenIs(token, TokenType.CLOSE_CURLY_BRACKET);
328
329 /// Test if this token is a OPEN_SQUARE_BRACKET token.
330 bool isOPEN_SQ_BRACKET(Token token) =>
331 tokenIs(token, TokenType.OPEN_SQUARE_BRACKET);
332
333 /// Test if this token is a CLOSE_SQUARE_BRACKET token.
334 bool isCLOSE_SQUARE_BRACKET(Token token) =>
335 tokenIs(token, TokenType.CLOSE_SQUARE_BRACKET);
336
337 /// Test if this token is a SEMICOLON token.
338 bool isSEMICOLON(Token token) =>
339 tokenIs(token, TokenType.SEMICOLON);
340
341
342 /// An AST visitor that drives formatting heuristics.
343 class SourceVisitor implements AstVisitor {
344
345 static final OPEN_CURLY = syntheticToken(TokenType.OPEN_CURLY_BRACKET, '{');
346 static final CLOSE_CURLY = syntheticToken(TokenType.CLOSE_CURLY_BRACKET, '}');
347 static final SEMI_COLON = syntheticToken(TokenType.SEMICOLON, ';');
348
349 static const SYNTH_OFFSET = -13;
350
351 static StringToken syntheticToken(TokenType type, String value) =>
352 new StringToken(type, value, SYNTH_OFFSET);
353
354 static bool isSynthetic(Token token) => token.offset == SYNTH_OFFSET;
355
356 /// The writer to which the source is to be written.
357 final SourceWriter writer;
358
359 /// Cached line info for calculating blank lines.
360 LineInfo lineInfo;
361
362 /// Cached previous token for calculating preceding whitespace.
363 Token previousToken;
364
365 /// A flag to indicate that a newline should be emitted before the next token.
366 bool needsNewline = false;
367
368 /// A flag to indicate that user introduced newlines should be emitted before
369 /// the next token.
370 bool preserveNewlines = false;
371
372 /// A counter for spaces that should be emitted preceding the next token.
373 int leadingSpaces = 0;
374
375 /// A flag to specify whether line-leading spaces should be preserved (and
376 /// addded to the indent level).
377 bool allowLineLeadingSpaces;
378
379 /// A flag to specify whether zero-length spaces should be emmitted.
380 bool emitEmptySpaces = false;
381
382 /// Used for matching EOL comments
383 final twoSlashes = new RegExp(r'//[^/]');
384
385 /// A weight for potential breakpoints.
386 int currentBreakWeight = DEFAULT_SPACE_WEIGHT;
387
388 /// The last issued space weight.
389 int lastSpaceWeight = 0;
390
391 /// Original pre-format selection information (may be null).
392 final Selection preSelection;
393
394 final bool codeTransforms;
395
396
397 /// The source being formatted (used in interpolation handling)
398 final String source;
399
400 /// Post format selection information.
401 Selection selection;
402
403
404 /// Initialize a newly created visitor to write source code representing
405 /// the visited nodes to the given [writer].
406 SourceVisitor(FormatterOptions options, this.lineInfo, this.source,
407 this.preSelection)
408 : writer = new SourceWriter(indentCount: options.initialIndentationLevel,
409 lineSeparator: options.lineSeparator,
410 maxLineLength: options.pageWidth,
411 useTabs: options.tabsForIndent,
412 spacesPerIndent: options.spacesPerIndent),
413 codeTransforms = options.codeTransforms;
414
415 visitAdjacentStrings(AdjacentStrings node) {
416 visitNodes(node.strings, separatedBy: space);
417 }
418
419 visitAnnotation(Annotation node) {
420 token(node.atSign);
421 visit(node.name);
422 token(node.period);
423 visit(node.constructorName);
424 visit(node.arguments);
425 }
426
427 visitArgumentList(ArgumentList node) {
428 token(node.leftParenthesis);
429 if (node.arguments.isNotEmpty) {
430 int weight = lastSpaceWeight++;
431 levelSpace(weight, 0);
432 visitCommaSeparatedNodes(
433 node.arguments,
434 followedBy: () => levelSpace(weight));
435 }
436 token(node.rightParenthesis);
437 }
438
439 visitAsExpression(AsExpression node) {
440 visit(node.expression);
441 space();
442 token(node.asOperator);
443 space();
444 visit(node.type);
445 }
446
447 visitAssertStatement(AssertStatement node) {
448 token(node.keyword);
449 token(node.leftParenthesis);
450 visit(node.condition);
451 token(node.rightParenthesis);
452 token(node.semicolon);
453 }
454
455 visitAssignmentExpression(AssignmentExpression node) {
456 visit(node.leftHandSide);
457 space();
458 token(node.operator);
459 allowContinuedLines((){
460 levelSpace(SINGLE_SPACE_WEIGHT);
461 visit(node.rightHandSide);
462 });
463 }
464
465 @override
466 visitAwaitExpression(AwaitExpression node) {
467 token(node.awaitKeyword);
468 space();
469 visit(node.expression);
470 }
471
472 visitBinaryExpression(BinaryExpression node) {
473 Token operator = node.operator;
474 TokenType operatorType = operator.type;
475 int addOperands(List<Expression> operands, Expression e, int i) {
476 if (e is BinaryExpression && e.operator.type == operatorType) {
477 i = addOperands(operands, e.leftOperand, i);
478 i = addOperands(operands, e.rightOperand, i);
479 } else {
480 operands.insert(i++, e);
481 }
482 return i;
483 }
484 List<Expression> operands = [];
485 addOperands(operands, node.leftOperand, 0);
486 addOperands(operands, node.rightOperand, operands.length);
487 int weight = lastSpaceWeight++;
488 for (int i = 0; i < operands.length; i++) {
489 if (i != 0) {
490 space();
491 token(operator);
492 levelSpace(weight);
493 }
494 visit(operands[i]);
495 }
496 }
497
498 visitBlock(Block node) {
499 token(node.leftBracket);
500 indent();
501 if (!node.statements.isEmpty) {
502 visitNodes(node.statements, precededBy: newlines, separatedBy: newlines);
503 newlines();
504 } else {
505 preserveLeadingNewlines();
506 }
507 token(node.rightBracket, precededBy: unindent);
508 }
509
510 visitBlockFunctionBody(BlockFunctionBody node) {
511 visit(node.block);
512 }
513
514 visitBooleanLiteral(BooleanLiteral node) {
515 token(node.literal);
516 }
517
518 visitBreakStatement(BreakStatement node) {
519 token(node.keyword);
520 visitNode(node.label, precededBy: space);
521 token(node.semicolon);
522 }
523
524 visitCascadeExpression(CascadeExpression node) {
525 visit(node.target);
526 indent(2);
527 // Single cascades do not force a linebreak (dartbug.com/16384)
528 if (node.cascadeSections.length > 1) {
529 newlines();
530 }
531 visitNodes(node.cascadeSections, separatedBy: newlines);
532 unindent(2);
533 }
534
535 visitCatchClause(CatchClause node) {
536
537 token(node.onKeyword, followedBy: space);
538 visit(node.exceptionType);
539
540 if (node.catchKeyword != null) {
541 if (node.exceptionType != null) {
542 space();
543 }
544 token(node.catchKeyword);
545 space();
546 token(node.leftParenthesis);
547 visit(node.exceptionParameter);
548 token(node.comma, followedBy: space);
549 visit(node.stackTraceParameter);
550 token(node.rightParenthesis);
551 space();
552 } else {
553 space();
554 }
555 visit(node.body);
556 }
557
558 visitClassDeclaration(ClassDeclaration node) {
559 preserveLeadingNewlines();
560 visitMemberMetadata(node.metadata);
561 modifier(node.abstractKeyword);
562 token(node.classKeyword);
563 space();
564 visit(node.name);
565 allowContinuedLines((){
566 visit(node.typeParameters);
567 visitNode(node.extendsClause, precededBy: space);
568 visitNode(node.withClause, precededBy: space);
569 visitNode(node.implementsClause, precededBy: space);
570 visitNode(node.nativeClause, precededBy: space);
571 space();
572 });
573 token(node.leftBracket);
574 indent();
575 if (!node.members.isEmpty) {
576 visitNodes(node.members, precededBy: newlines, separatedBy: newlines);
577 newlines();
578 } else {
579 preserveLeadingNewlines();
580 }
581 token(node.rightBracket, precededBy: unindent);
582 }
583
584 visitClassTypeAlias(ClassTypeAlias node) {
585 preserveLeadingNewlines();
586 visitMemberMetadata(node.metadata);
587 modifier(node.abstractKeyword);
588 token(node.keyword);
589 space();
590 visit(node.name);
591 visit(node.typeParameters);
592 space();
593 token(node.equals);
594 space();
595 visit(node.superclass);
596 visitNode(node.withClause, precededBy: space);
597 visitNode(node.implementsClause, precededBy: space);
598 token(node.semicolon);
599 }
600
601 visitComment(Comment node) => null;
602
603 visitCommentReference(CommentReference node) => null;
604
605 visitCompilationUnit(CompilationUnit node) {
606
607 // Cache EOF for leading whitespace calculation
608 var start = node.beginToken.previous;
609 if (start != null && start.type is TokenType_EOF) {
610 previousToken = start;
611 }
612
613 var scriptTag = node.scriptTag;
614 var directives = node.directives;
615 visit(scriptTag);
616
617 visitNodes(directives, separatedBy: newlines, followedBy: newlines);
618
619 visitNodes(node.declarations, separatedBy: newlines);
620
621 preserveLeadingNewlines();
622
623 // Handle trailing whitespace
624 token(node.endToken /* EOF */);
625
626 // Be a good citizen, end with a NL
627 ensureTrailingNewline();
628 }
629
630 visitConditionalExpression(ConditionalExpression node) {
631 int weight = lastSpaceWeight++;
632 visit(node.condition);
633 space();
634 token(node.question);
635 allowContinuedLines((){
636 levelSpace(weight);
637 visit(node.thenExpression);
638 space();
639 token(node.colon);
640 levelSpace(weight);
641 visit(node.elseExpression);
642 });
643 }
644
645 visitConstructorDeclaration(ConstructorDeclaration node) {
646 visitMemberMetadata(node.metadata);
647 modifier(node.externalKeyword);
648 modifier(node.constKeyword);
649 modifier(node.factoryKeyword);
650 visit(node.returnType);
651 token(node.period);
652 visit(node.name);
653 visit(node.parameters);
654
655 // Check for redirects or initializer lists
656 if (node.separator != null) {
657 if (node.redirectedConstructor != null) {
658 visitConstructorRedirects(node);
659 } else {
660 visitConstructorInitializers(node);
661 }
662 }
663
664 var body = node.body;
665 if (codeTransforms && body is BlockFunctionBody) {
666 if (body.block.statements.isEmpty) {
667 token(SEMI_COLON);
668 newlines();
669 return;
670 }
671 }
672
673 visitPrefixedBody(space, body);
674 }
675
676 visitConstructorInitializers(ConstructorDeclaration node) {
677 if (node.initializers.length > 1) {
678 newlines();
679 } else {
680 preserveLeadingNewlines();
681 levelSpace(lastSpaceWeight++);
682 }
683 indent(2);
684 token(node.separator /* : */);
685 space();
686 for (var i = 0; i < node.initializers.length; i++) {
687 if (i > 0) {
688 // preceding comma
689 token(node.initializers[i].beginToken.previous);
690 newlines();
691 space(n: 2, allowLineLeading: true);
692 }
693 node.initializers[i].accept(this);
694 }
695 unindent(2);
696 }
697
698 visitConstructorRedirects(ConstructorDeclaration node) {
699 token(node.separator /* = */, precededBy: space, followedBy: space);
700 visitCommaSeparatedNodes(node.initializers);
701 visit(node.redirectedConstructor);
702 }
703
704 visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
705 token(node.keyword);
706 token(node.period);
707 visit(node.fieldName);
708 space();
709 token(node.equals);
710 space();
711 visit(node.expression);
712 }
713
714 visitConstructorName(ConstructorName node) {
715 visit(node.type);
716 token(node.period);
717 visit(node.name);
718 }
719
720 visitContinueStatement(ContinueStatement node) {
721 token(node.keyword);
722 visitNode(node.label, precededBy: space);
723 token(node.semicolon);
724 }
725
726 visitDeclaredIdentifier(DeclaredIdentifier node) {
727 modifier(node.keyword);
728 visitNode(node.type, followedBy: space);
729 visit(node.identifier);
730 }
731
732 visitDefaultFormalParameter(DefaultFormalParameter node) {
733 visit(node.parameter);
734 if (node.separator != null) {
735 // The '=' separator is preceded by a space
736 if (node.separator.type == TokenType.EQ) {
737 space();
738 }
739 token(node.separator);
740 visitNode(node.defaultValue, precededBy: space);
741 }
742 }
743
744 visitDoStatement(DoStatement node) {
745 token(node.doKeyword);
746 space();
747 visit(node.body);
748 space();
749 token(node.whileKeyword);
750 space();
751 token(node.leftParenthesis);
752 allowContinuedLines((){
753 visit(node.condition);
754 token(node.rightParenthesis);
755 });
756 token(node.semicolon);
757 }
758
759 visitDoubleLiteral(DoubleLiteral node) {
760 token(node.literal);
761 }
762
763 visitEmptyFunctionBody(EmptyFunctionBody node) {
764 token(node.semicolon);
765 }
766
767 visitEmptyStatement(EmptyStatement node) {
768 if (!codeTransforms || node.parent is! Block) {
769 token(node.semicolon);
770 }
771 }
772
773 visitExportDirective(ExportDirective node) {
774 visitDirectiveMetadata(node.metadata);
775 token(node.keyword);
776 space();
777 visit(node.uri);
778 allowContinuedLines((){
779 visitNodes(node.combinators, precededBy: space, separatedBy: space);
780 });
781 token(node.semicolon);
782 }
783
784 visitExpressionFunctionBody(ExpressionFunctionBody node) {
785 int weight = lastSpaceWeight++;
786 token(node.functionDefinition);
787 levelSpace(weight);
788 visit(node.expression);
789 token(node.semicolon);
790 }
791
792 visitExpressionStatement(ExpressionStatement node) {
793 visit(node.expression);
794 token(node.semicolon);
795 }
796
797 visitExtendsClause(ExtendsClause node) {
798 token(node.keyword);
799 space();
800 visit(node.superclass);
801 }
802
803 visitFieldDeclaration(FieldDeclaration node) {
804 visitMemberMetadata(node.metadata);
805 modifier(node.staticKeyword);
806 visit(node.fields);
807 token(node.semicolon);
808 }
809
810 visitFieldFormalParameter(FieldFormalParameter node) {
811 token(node.keyword, followedBy: space);
812 visitNode(node.type, followedBy: space);
813 token(node.thisToken);
814 token(node.period);
815 visit(node.identifier);
816 visit(node.parameters);
817 }
818
819 visitForEachStatement(ForEachStatement node) {
820 token(node.forKeyword);
821 space();
822 token(node.leftParenthesis);
823 if (node.loopVariable != null) {
824 visit(node.loopVariable);
825 } else {
826 visit(node.identifier);
827 }
828 space();
829 token(node.inKeyword);
830 space();
831 visit(node.iterator);
832 token(node.rightParenthesis);
833 space();
834 visit(node.body);
835 }
836
837 visitFormalParameterList(FormalParameterList node) {
838 var groupEnd = null;
839 token(node.leftParenthesis);
840 var parameters = node.parameters;
841 var size = parameters.length;
842 for (var i = 0; i < size; i++) {
843 var parameter = parameters[i];
844 if (i > 0) {
845 append(',');
846 space();
847 }
848 if (groupEnd == null && parameter is DefaultFormalParameter) {
849 if (identical(parameter.kind, ParameterKind.NAMED)) {
850 groupEnd = '}';
851 append('{');
852 } else {
853 groupEnd = ']';
854 append('[');
855 }
856 }
857 parameter.accept(this);
858 }
859 if (groupEnd != null) {
860 append(groupEnd);
861 }
862 token(node.rightParenthesis);
863 }
864
865 visitForStatement(ForStatement node) {
866 token(node.forKeyword);
867 space();
868 token(node.leftParenthesis);
869 if (node.initialization != null) {
870 visit(node.initialization);
871 } else {
872 if (node.variables == null) {
873 space();
874 } else {
875 visit(node.variables);
876 }
877 }
878 token(node.leftSeparator);
879 space();
880 visit(node.condition);
881 token(node.rightSeparator);
882 if (node.updaters != null) {
883 space();
884 visitCommaSeparatedNodes(node.updaters);
885 }
886 token(node.rightParenthesis);
887 if (node.body is! EmptyStatement) {
888 space();
889 }
890 visit(node.body);
891 }
892
893 visitFunctionDeclaration(FunctionDeclaration node) {
894 preserveLeadingNewlines();
895 visitMemberMetadata(node.metadata);
896 modifier(node.externalKeyword);
897 visitNode(node.returnType, followedBy: space);
898 modifier(node.propertyKeyword);
899 visit(node.name);
900 visit(node.functionExpression);
901 }
902
903 visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
904 visit(node.functionDeclaration);
905 }
906
907 visitFunctionExpression(FunctionExpression node) {
908 visit(node.parameters);
909 if (node.body is! EmptyFunctionBody) {
910 space();
911 }
912 visit(node.body);
913 }
914
915 visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
916 visit(node.function);
917 visit(node.argumentList);
918 }
919
920 visitFunctionTypeAlias(FunctionTypeAlias node) {
921 visitMemberMetadata(node.metadata);
922 token(node.keyword);
923 space();
924 visitNode(node.returnType, followedBy: space);
925 visit(node.name);
926 visit(node.typeParameters);
927 visit(node.parameters);
928 token(node.semicolon);
929 }
930
931 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
932 visitNode(node.returnType, followedBy: space);
933 visit(node.identifier);
934 visit(node.parameters);
935 }
936
937 visitHideCombinator(HideCombinator node) {
938 token(node.keyword);
939 space();
940 visitCommaSeparatedNodes(node.hiddenNames);
941 }
942
943 visitIfStatement(IfStatement node) {
944 var hasElse = node.elseStatement != null;
945 token(node.ifKeyword);
946 allowContinuedLines((){
947 space();
948 token(node.leftParenthesis);
949 visit(node.condition);
950 token(node.rightParenthesis);
951 });
952 space();
953 if (hasElse) {
954 printAsBlock(node.thenStatement);
955 space();
956 token(node.elseKeyword);
957 space();
958 if (node.elseStatement is IfStatement) {
959 visit(node.elseStatement);
960 } else {
961 printAsBlock(node.elseStatement);
962 }
963 } else {
964 visit(node.thenStatement);
965 }
966 }
967
968 visitImplementsClause(ImplementsClause node) {
969 token(node.keyword);
970 space();
971 visitCommaSeparatedNodes(node.interfaces);
972 }
973
974 visitImportDirective(ImportDirective node) {
975 visitDirectiveMetadata(node.metadata);
976 token(node.keyword);
977 nonBreakingSpace();
978 visit(node.uri);
979 token(node.deferredToken, precededBy: space);
980 token(node.asToken, precededBy: space, followedBy: space);
981 allowContinuedLines((){
982 visit(node.prefix);
983 visitNodes(node.combinators, precededBy: space, separatedBy: space);
984 });
985 token(node.semicolon);
986 }
987
988 visitIndexExpression(IndexExpression node) {
989 if (node.isCascaded) {
990 token(node.period);
991 } else {
992 visit(node.target);
993 }
994 token(node.leftBracket);
995 visit(node.index);
996 token(node.rightBracket);
997 }
998
999 visitInstanceCreationExpression(InstanceCreationExpression node) {
1000 token(node.keyword);
1001 nonBreakingSpace();
1002 visit(node.constructorName);
1003 visit(node.argumentList);
1004 }
1005
1006 visitIntegerLiteral(IntegerLiteral node) {
1007 token(node.literal);
1008 }
1009
1010 visitInterpolationExpression(InterpolationExpression node) {
1011 if (node.rightBracket != null) {
1012 token(node.leftBracket);
1013 visit(node.expression);
1014 token(node.rightBracket);
1015 } else {
1016 token(node.leftBracket);
1017 visit(node.expression);
1018 }
1019 }
1020
1021 visitInterpolationString(InterpolationString node) {
1022 token(node.contents);
1023 }
1024
1025 visitIsExpression(IsExpression node) {
1026 visit(node.expression);
1027 space();
1028 token(node.isOperator);
1029 token(node.notOperator);
1030 space();
1031 visit(node.type);
1032 }
1033
1034 visitLabel(Label node) {
1035 visit(node.label);
1036 token(node.colon);
1037 }
1038
1039 visitLabeledStatement(LabeledStatement node) {
1040 visitNodes(node.labels, separatedBy: space, followedBy: space);
1041 visit(node.statement);
1042 }
1043
1044 visitLibraryDirective(LibraryDirective node) {
1045 visitDirectiveMetadata(node.metadata);
1046 token(node.keyword);
1047 space();
1048 visit(node.name);
1049 token(node.semicolon);
1050 }
1051
1052 visitLibraryIdentifier(LibraryIdentifier node) {
1053 append(node.name);
1054 }
1055
1056 visitListLiteral(ListLiteral node) {
1057 int weight = lastSpaceWeight++;
1058 modifier(node.constKeyword);
1059 visit(node.typeArguments);
1060 token(node.leftBracket);
1061 indent();
1062 levelSpace(weight, 0);
1063 visitCommaSeparatedNodes(
1064 node.elements,
1065 followedBy: () => levelSpace(weight));
1066 optionalTrailingComma(node.rightBracket);
1067 token(node.rightBracket, precededBy: unindent);
1068 }
1069
1070 visitMapLiteral(MapLiteral node) {
1071 modifier(node.constKeyword);
1072 visitNode(node.typeArguments);
1073 token(node.leftBracket);
1074 if (!node.entries.isEmpty) {
1075 newlines();
1076 indent();
1077 visitCommaSeparatedNodes(node.entries, followedBy: newlines);
1078 optionalTrailingComma(node.rightBracket);
1079 unindent();
1080 newlines();
1081 }
1082 token(node.rightBracket);
1083 }
1084
1085 visitMapLiteralEntry(MapLiteralEntry node) {
1086 visit(node.key);
1087 token(node.separator);
1088 space();
1089 visit(node.value);
1090 }
1091
1092 visitMethodDeclaration(MethodDeclaration node) {
1093 visitMemberMetadata(node.metadata);
1094 modifier(node.externalKeyword);
1095 modifier(node.modifierKeyword);
1096 visitNode(node.returnType, followedBy: space);
1097 modifier(node.propertyKeyword);
1098 modifier(node.operatorKeyword);
1099 visit(node.name);
1100 if (!node.isGetter) {
1101 visit(node.parameters);
1102 }
1103 visitPrefixedBody(nonBreakingSpace, node.body);
1104 }
1105
1106 visitMethodInvocation(MethodInvocation node) {
1107 visit(node.target);
1108 token(node.period);
1109 visit(node.methodName);
1110 visit(node.argumentList);
1111 }
1112
1113 visitNamedExpression(NamedExpression node) {
1114 visit(node.name);
1115 visitNode(node.expression, precededBy: space);
1116 }
1117
1118 visitNativeClause(NativeClause node) {
1119 token(node.keyword);
1120 space();
1121 visit(node.name);
1122 }
1123
1124 visitNativeFunctionBody(NativeFunctionBody node) {
1125 token(node.nativeToken);
1126 space();
1127 visit(node.stringLiteral);
1128 token(node.semicolon);
1129 }
1130
1131 visitNullLiteral(NullLiteral node) {
1132 token(node.literal);
1133 }
1134
1135 visitParenthesizedExpression(ParenthesizedExpression node) {
1136 token(node.leftParenthesis);
1137 visit(node.expression);
1138 token(node.rightParenthesis);
1139 }
1140
1141 visitPartDirective(PartDirective node) {
1142 token(node.keyword);
1143 space();
1144 visit(node.uri);
1145 token(node.semicolon);
1146 }
1147
1148 visitPartOfDirective(PartOfDirective node) {
1149 token(node.keyword);
1150 space();
1151 token(node.ofToken);
1152 space();
1153 visit(node.libraryName);
1154 token(node.semicolon);
1155 }
1156
1157 visitPostfixExpression(PostfixExpression node) {
1158 visit(node.operand);
1159 token(node.operator);
1160 }
1161
1162 visitPrefixedIdentifier(PrefixedIdentifier node) {
1163 visit(node.prefix);
1164 token(node.period);
1165 visit(node.identifier);
1166 }
1167
1168 visitPrefixExpression(PrefixExpression node) {
1169 token(node.operator);
1170 visit(node.operand);
1171 }
1172
1173 visitPropertyAccess(PropertyAccess node) {
1174 if (node.isCascaded) {
1175 token(node.operator);
1176 } else {
1177 visit(node.target);
1178 token(node.operator);
1179 }
1180 visit(node.propertyName);
1181 }
1182
1183 visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
1184 token(node.keyword);
1185 token(node.period);
1186 visit(node.constructorName);
1187 visit(node.argumentList);
1188 }
1189
1190 visitRethrowExpression(RethrowExpression node) {
1191 token(node.keyword);
1192 }
1193
1194 visitReturnStatement(ReturnStatement node) {
1195 var expression = node.expression;
1196 if (expression == null) {
1197 token(node.keyword);
1198 token(node.semicolon);
1199 } else {
1200 token(node.keyword);
1201 allowContinuedLines((){
1202 space();
1203 expression.accept(this);
1204 token(node.semicolon);
1205 });
1206 }
1207 }
1208
1209 visitScriptTag(ScriptTag node) {
1210 token(node.scriptTag);
1211 }
1212
1213 visitShowCombinator(ShowCombinator node) {
1214 token(node.keyword);
1215 space();
1216 visitCommaSeparatedNodes(node.shownNames);
1217 }
1218
1219 visitSimpleFormalParameter(SimpleFormalParameter node) {
1220 visitMemberMetadata(node.metadata);
1221 modifier(node.keyword);
1222 visitNode(node.type, followedBy: nonBreakingSpace);
1223 visit(node.identifier);
1224 }
1225
1226 visitSimpleIdentifier(SimpleIdentifier node) {
1227 token(node.token);
1228 }
1229
1230 visitSimpleStringLiteral(SimpleStringLiteral node) {
1231 token(node.literal);
1232 }
1233
1234 visitStringInterpolation(StringInterpolation node) {
1235 // Ensure that interpolated strings don't get broken up by treating them as
1236 // a single String token
1237 // Process token (for comments etc. but don't print the lexeme)
1238 token(node.beginToken, printToken: (tok) => null);
1239 var start = node.beginToken.offset;
1240 var end = node.endToken.end;
1241 String string = source.substring(start, end);
1242 append(string);
1243 //visitNodes(node.elements);
1244 }
1245
1246 visitSuperConstructorInvocation(SuperConstructorInvocation node) {
1247 token(node.keyword);
1248 token(node.period);
1249 visit(node.constructorName);
1250 visit(node.argumentList);
1251 }
1252
1253 visitSuperExpression(SuperExpression node) {
1254 token(node.keyword);
1255 }
1256
1257 visitSwitchCase(SwitchCase node) {
1258 visitNodes(node.labels, separatedBy: space, followedBy: space);
1259 token(node.keyword);
1260 space();
1261 visit(node.expression);
1262 token(node.colon);
1263 newlines();
1264 indent();
1265 visitNodes(node.statements, separatedBy: newlines);
1266 unindent();
1267 }
1268
1269 visitSwitchDefault(SwitchDefault node) {
1270 visitNodes(node.labels, separatedBy: space, followedBy: space);
1271 token(node.keyword);
1272 token(node.colon);
1273 newlines();
1274 indent();
1275 visitNodes(node.statements, separatedBy: newlines);
1276 unindent();
1277 }
1278
1279 visitSwitchStatement(SwitchStatement node) {
1280 token(node.keyword);
1281 space();
1282 token(node.leftParenthesis);
1283 visit(node.expression);
1284 token(node.rightParenthesis);
1285 space();
1286 token(node.leftBracket);
1287 indent();
1288 newlines();
1289 visitNodes(node.members, separatedBy: newlines, followedBy: newlines);
1290 token(node.rightBracket, precededBy: unindent);
1291
1292 }
1293
1294 visitSymbolLiteral(SymbolLiteral node) {
1295 token(node.poundSign);
1296 var components = node.components;
1297 var size = components.length;
1298 for (var component in components) {
1299 // The '.' separator
1300 if (component.previous.lexeme == '.') {
1301 token(component.previous);
1302 }
1303 token(component);
1304 }
1305 }
1306
1307 visitThisExpression(ThisExpression node) {
1308 token(node.keyword);
1309 }
1310
1311 visitThrowExpression(ThrowExpression node) {
1312 token(node.keyword);
1313 space();
1314 visit(node.expression);
1315 }
1316
1317 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
1318 visit(node.variables);
1319 token(node.semicolon);
1320 }
1321
1322 visitTryStatement(TryStatement node) {
1323 token(node.tryKeyword);
1324 space();
1325 visit(node.body);
1326 visitNodes(node.catchClauses, precededBy: space, separatedBy: space);
1327 token(node.finallyKeyword, precededBy: space, followedBy: space);
1328 visit(node.finallyBlock);
1329 }
1330
1331 visitTypeArgumentList(TypeArgumentList node) {
1332 token(node.leftBracket);
1333 visitCommaSeparatedNodes(node.arguments);
1334 token(node.rightBracket);
1335 }
1336
1337 visitTypeName(TypeName node) {
1338 visit(node.name);
1339 visit(node.typeArguments);
1340 }
1341
1342 visitTypeParameter(TypeParameter node) {
1343 visitMemberMetadata(node.metadata);
1344 visit(node.name);
1345 token(node.keyword /* extends */, precededBy: space, followedBy: space);
1346 visit(node.bound);
1347 }
1348
1349 visitTypeParameterList(TypeParameterList node) {
1350 token(node.leftBracket);
1351 visitCommaSeparatedNodes(node.typeParameters);
1352 token(node.rightBracket);
1353 }
1354
1355 visitVariableDeclaration(VariableDeclaration node) {
1356 visit(node.name);
1357 if (node.initializer != null) {
1358 space();
1359 token(node.equals);
1360 var initializer = node.initializer;
1361 if (initializer is ListLiteral || initializer is MapLiteral) {
1362 space();
1363 visit(initializer);
1364 } else if (initializer is BinaryExpression) {
1365 allowContinuedLines(() {
1366 levelSpace(lastSpaceWeight);
1367 visit(initializer);
1368 });
1369 } else {
1370 allowContinuedLines(() {
1371 levelSpace(SINGLE_SPACE_WEIGHT);
1372 visit(initializer);
1373 });
1374 }
1375 }
1376 }
1377
1378 visitVariableDeclarationList(VariableDeclarationList node) {
1379 visitMemberMetadata(node.metadata);
1380 modifier(node.keyword);
1381 visitNode(node.type, followedBy: space);
1382
1383 var variables = node.variables;
1384 // Decls with initializers get their own lines (dartbug.com/16849)
1385 if (variables.any((v) => (v.initializer != null))) {
1386 var size = variables.length;
1387 if (size > 0) {
1388 var variable;
1389 for (var i = 0; i < size; i++) {
1390 variable = variables[i];
1391 if (i > 0) {
1392 var comma = variable.beginToken.previous;
1393 token(comma);
1394 newlines();
1395 }
1396 if (i == 1) {
1397 indent(2);
1398 }
1399 variable.accept(this);
1400 }
1401 if (size > 1) {
1402 unindent(2);
1403 }
1404 }
1405 } else {
1406 visitCommaSeparatedNodes(node.variables);
1407 }
1408 }
1409
1410 visitVariableDeclarationStatement(VariableDeclarationStatement node) {
1411 visit(node.variables);
1412 token(node.semicolon);
1413 }
1414
1415 visitWhileStatement(WhileStatement node) {
1416 token(node.keyword);
1417 space();
1418 token(node.leftParenthesis);
1419 allowContinuedLines((){
1420 visit(node.condition);
1421 token(node.rightParenthesis);
1422 });
1423 if (node.body is! EmptyStatement) {
1424 space();
1425 }
1426 visit(node.body);
1427 }
1428
1429 visitWithClause(WithClause node) {
1430 token(node.withKeyword);
1431 space();
1432 visitCommaSeparatedNodes(node.mixinTypes);
1433 }
1434
1435 @override
1436 visitYieldStatement(YieldStatement node) {
1437 token(node.yieldKeyword);
1438 token(node.star);
1439 space();
1440 visit(node.expression);
1441 token(node.semicolon);
1442 }
1443
1444 /// Safely visit the given [node].
1445 visit(AstNode node) {
1446 if (node != null) {
1447 node.accept(this);
1448 }
1449 }
1450
1451 /// Visit member metadata
1452 visitMemberMetadata(NodeList<Annotation> metadata) {
1453 visitNodes(metadata,
1454 separatedBy: () {
1455 space();
1456 preserveLeadingNewlines();
1457 },
1458 followedBy: space);
1459 if (metadata != null && metadata.length > 0) {
1460 preserveLeadingNewlines();
1461 }
1462 }
1463
1464 /// Visit member metadata
1465 visitDirectiveMetadata(NodeList<Annotation> metadata) {
1466 visitNodes(metadata, separatedBy: newlines, followedBy: newlines);
1467 }
1468
1469 /// Visit the given function [body], printing the [prefix] before if given
1470 /// body is not empty.
1471 visitPrefixedBody(prefix(), FunctionBody body) {
1472 if (body is! EmptyFunctionBody) {
1473 prefix();
1474 }
1475 visit(body);
1476 }
1477
1478 /// Visit a list of [nodes] if not null, optionally separated and/or preceded
1479 /// and followed by the given functions.
1480 visitNodes(NodeList<AstNode> nodes, {precededBy(): null,
1481 separatedBy() : null, followedBy(): null}) {
1482 if (nodes != null) {
1483 var size = nodes.length;
1484 if (size > 0) {
1485 if (precededBy != null) {
1486 precededBy();
1487 }
1488 for (var i = 0; i < size; i++) {
1489 if (i > 0 && separatedBy != null) {
1490 separatedBy();
1491 }
1492 nodes[i].accept(this);
1493 }
1494 if (followedBy != null) {
1495 followedBy();
1496 }
1497 }
1498 }
1499 }
1500
1501 /// Visit a comma-separated list of [nodes] if not null.
1502 visitCommaSeparatedNodes(NodeList<AstNode> nodes, {followedBy(): null}) {
1503 //TODO(pquitslund): handle this more neatly
1504 if (followedBy == null) {
1505 followedBy = space;
1506 }
1507 if (nodes != null) {
1508 var size = nodes.length;
1509 if (size > 0) {
1510 var node;
1511 for (var i = 0; i < size; i++) {
1512 node = nodes[i];
1513 if (i > 0) {
1514 var comma = node.beginToken.previous;
1515 token(comma);
1516 followedBy();
1517 }
1518 node.accept(this);
1519 }
1520 }
1521 }
1522 }
1523
1524
1525 /// Visit a [node], and if not null, optionally preceded or followed by the
1526 /// specified functions.
1527 visitNode(AstNode node, {precededBy(): null, followedBy(): null}) {
1528 if (node != null) {
1529 if (precededBy != null) {
1530 precededBy();
1531 }
1532 node.accept(this);
1533 if (followedBy != null) {
1534 followedBy();
1535 }
1536 }
1537 }
1538
1539 /// Allow [code] to be continued across lines.
1540 allowContinuedLines(code()) {
1541 //TODO(pquitslund): add before
1542 code();
1543 //TODO(pquitslund): add after
1544 }
1545
1546 /// Emit the given [modifier] if it's non null, followed by non-breaking
1547 /// whitespace.
1548 modifier(Token modifier) {
1549 token(modifier, followedBy: space);
1550 }
1551
1552 /// Indicate that at least one newline should be emitted and possibly more
1553 /// if the source has them.
1554 newlines() {
1555 needsNewline = true;
1556 }
1557
1558 /// Optionally emit a trailing comma.
1559 optionalTrailingComma(Token rightBracket) {
1560 if (rightBracket.previous.lexeme == ',') {
1561 token(rightBracket.previous);
1562 }
1563 }
1564
1565 /// Indicate that user introduced newlines should be emitted before the next
1566 /// token.
1567 preserveLeadingNewlines() {
1568 preserveNewlines = true;
1569 }
1570
1571 token(Token token, {precededBy(), followedBy(), printToken(tok),
1572 int minNewlines: 0}) {
1573 if (token != null) {
1574 if (needsNewline) {
1575 minNewlines = max(1, minNewlines);
1576 }
1577 var emitted = emitPrecedingCommentsAndNewlines(token, min: minNewlines);
1578 if (emitted > 0) {
1579 needsNewline = false;
1580 }
1581 if (precededBy != null) {
1582 precededBy();
1583 }
1584 checkForSelectionUpdate(token);
1585 if (printToken == null) {
1586 append(token.lexeme);
1587 } else {
1588 printToken(token);
1589 }
1590 if (followedBy != null) {
1591 followedBy();
1592 }
1593 previousToken = token;
1594 }
1595 }
1596
1597 emitSpaces() {
1598 if (leadingSpaces > 0 || emitEmptySpaces) {
1599 if (allowLineLeadingSpaces || !writer.currentLine.isWhitespace()) {
1600 writer.spaces(leadingSpaces, breakWeight: currentBreakWeight);
1601 }
1602 leadingSpaces = 0;
1603 allowLineLeadingSpaces = false;
1604 emitEmptySpaces = false;
1605 currentBreakWeight = DEFAULT_SPACE_WEIGHT;
1606 }
1607 }
1608
1609 checkForSelectionUpdate(Token token) {
1610 // Cache the first token on or AFTER the selection offset
1611 if (preSelection != null && selection == null) {
1612 // Check for overshots
1613 var overshot = token.offset - preSelection.offset;
1614 if (overshot >= 0) {
1615 //TODO(pquitslund): update length (may need truncating)
1616 selection = new Selection(
1617 writer.toString().length + leadingSpaces - overshot,
1618 preSelection.length);
1619 }
1620 }
1621 }
1622
1623 /// Emit a breakable 'non' (zero-length) space
1624 breakableNonSpace() {
1625 space(n: 0);
1626 emitEmptySpaces = true;
1627 }
1628
1629 /// Emit level spaces, even if empty (works as a break point).
1630 levelSpace(int weight, [int n = 1]) {
1631 space(n: n, breakWeight: weight);
1632 emitEmptySpaces = true;
1633 }
1634
1635 /// Emit a non-breakable space.
1636 nonBreakingSpace() {
1637 space(breakWeight: UNBREAKABLE_SPACE_WEIGHT);
1638 }
1639
1640 /// Emit a space. If [allowLineLeading] is specified, spaces
1641 /// will be preserved at the start of a line (in addition to the
1642 /// indent-level), otherwise line-leading spaces will be ignored.
1643 space({n: 1, allowLineLeading: false, breakWeight: DEFAULT_SPACE_WEIGHT}) {
1644 //TODO(pquitslund): replace with a proper space token
1645 leadingSpaces += n;
1646 allowLineLeadingSpaces = allowLineLeading;
1647 currentBreakWeight = breakWeight;
1648 }
1649
1650 /// Append the given [string] to the source writer if it's non-null.
1651 append(String string) {
1652 if (string != null && !string.isEmpty) {
1653 emitSpaces();
1654 writer.write(string);
1655 }
1656 }
1657
1658 /// Indent.
1659 indent([n = 1]) {
1660 while (n-- > 0) {
1661 writer.indent();
1662 }
1663 }
1664
1665 /// Unindent
1666 unindent([n = 1]) {
1667 while (n-- > 0) {
1668 writer.unindent();
1669 }
1670 }
1671
1672 /// Print this statement as if it were a block (e.g., surrounded by braces).
1673 printAsBlock(Statement statement) {
1674 if (codeTransforms && statement is! Block) {
1675 token(OPEN_CURLY);
1676 indent();
1677 newlines();
1678 visit(statement);
1679 newlines();
1680 token(CLOSE_CURLY, precededBy: unindent);
1681 } else {
1682 visit(statement);
1683 }
1684 }
1685
1686 /// Emit any detected comments and newlines or a minimum as specified
1687 /// by [min].
1688 int emitPrecedingCommentsAndNewlines(Token token, {min: 0}) {
1689
1690 var comment = token.precedingComments;
1691 var currentToken = comment != null ? comment : token;
1692
1693 //Handle EOLs before newlines
1694 if (isAtEOL(comment)) {
1695 emitComment(comment, previousToken);
1696 comment = comment.next;
1697 currentToken = comment != null ? comment : token;
1698 // Ensure EOL comments force a linebreak
1699 needsNewline = true;
1700 }
1701
1702 var lines = 0;
1703 if (needsNewline || preserveNewlines) {
1704 lines = max(min, countNewlinesBetween(previousToken, currentToken));
1705 preserveNewlines = false;
1706 }
1707
1708 emitNewlines(lines);
1709
1710 previousToken =
1711 currentToken.previous != null ? currentToken.previous : token.previous;
1712
1713 while (comment != null) {
1714
1715 emitComment(comment, previousToken);
1716
1717 var nextToken = comment.next != null ? comment.next : token;
1718 var newlines = calculateNewlinesBetweenComments(comment, nextToken);
1719 if (newlines > 0) {
1720 emitNewlines(newlines);
1721 lines += newlines;
1722 } else {
1723 var spaces = countSpacesBetween(comment, nextToken);
1724 if (spaces > 0) {
1725 space();
1726 }
1727 }
1728
1729 previousToken = comment;
1730 comment = comment.next;
1731 }
1732
1733 previousToken = token;
1734 return lines;
1735 }
1736
1737 void emitNewlines(lines) {
1738 writer.newlines(lines);
1739 }
1740
1741 ensureTrailingNewline() {
1742 if (writer.lastToken is! NewlineToken) {
1743 writer.newline();
1744 }
1745 }
1746
1747
1748 /// Test if this EOL [comment] is at the beginning of a line.
1749 bool isAtBOL(Token comment) =>
1750 lineInfo.getLocation(comment.offset).columnNumber == 1;
1751
1752 /// Test if this [comment] is at the end of a line.
1753 bool isAtEOL(Token comment) =>
1754 comment != null && comment.toString().trim().startsWith(twoSlashes) &&
1755 sameLine(comment, previousToken);
1756
1757 /// Emit this [comment], inserting leading whitespace if appropriate.
1758 emitComment(Token comment, Token previousToken) {
1759 if (!writer.currentLine.isWhitespace() && previousToken != null) {
1760 var ws = countSpacesBetween(previousToken, comment);
1761 // Preserve one space but no more
1762 if (ws > 0 && leadingSpaces == 0) {
1763 space();
1764 }
1765 }
1766
1767 // Don't indent commented-out lines
1768 if (isAtBOL(comment)) {
1769 writer.currentLine.clear();
1770 }
1771
1772 append(comment.toString().trim());
1773 }
1774
1775 /// Count spaces between these tokens. Tokens on different lines return 0.
1776 int countSpacesBetween(Token last, Token current) => isEOF(last) ||
1777 countNewlinesBetween(last, current) > 0 ? 0 : current.offset - last.end;
1778
1779 /// Count the blanks between these two nodes.
1780 int countBlankLinesBetween(AstNode lastNode, AstNode currentNode) =>
1781 countNewlinesBetween(lastNode.endToken, currentNode.beginToken);
1782
1783 /// Count newlines preceeding this [node].
1784 int countPrecedingNewlines(AstNode node) =>
1785 countNewlinesBetween(node.beginToken.previous, node.beginToken);
1786
1787 /// Count newlines succeeding this [node].
1788 int countSucceedingNewlines(AstNode node) => node == null ? 0 :
1789 countNewlinesBetween(node.endToken, node.endToken.next);
1790
1791 /// Count the blanks between these two tokens.
1792 int countNewlinesBetween(Token last, Token current) {
1793 if (last == null || current == null || isSynthetic(last)) {
1794 return 0;
1795 }
1796
1797 return linesBetween(last.end - 1, current.offset);
1798 }
1799
1800 /// Calculate the newlines that should separate these comments.
1801 int calculateNewlinesBetweenComments(Token last, Token current) {
1802 // Insist on a newline after doc comments or single line comments
1803 // (NOTE that EOL comments have already been processed).
1804 if (isOldSingleLineDocComment(last) || isSingleLineComment(last)) {
1805 return max(1, countNewlinesBetween(last, current));
1806 } else {
1807 return countNewlinesBetween(last, current);
1808 }
1809 }
1810
1811 /// Single line multi-line comments (e.g., '/** like this */').
1812 bool isOldSingleLineDocComment(Token comment) =>
1813 comment.lexeme.startsWith(r'/**') && singleLine(comment);
1814
1815 /// Test if this [token] spans just one line.
1816 bool singleLine(Token token) => linesBetween(token.offset, token.end) < 1;
1817
1818 /// Test if token [first] is on the same line as [second].
1819 bool sameLine(Token first, Token second) =>
1820 countNewlinesBetween(first, second) == 0;
1821
1822 /// Test if this is a multi-line [comment] (e.g., '/* ...' or '/** ...')
1823 bool isMultiLineComment(Token comment) =>
1824 comment.type == TokenType.MULTI_LINE_COMMENT;
1825
1826 /// Test if this is a single-line [comment] (e.g., '// ...')
1827 bool isSingleLineComment(Token comment) =>
1828 comment.type == TokenType.SINGLE_LINE_COMMENT;
1829
1830 /// Test if this [comment] is a block comment (e.g., '/* like this */')..
1831 bool isBlock(Token comment) =>
1832 isMultiLineComment(comment) && singleLine(comment);
1833
1834 /// Count the lines between two offsets.
1835 int linesBetween(int lastOffset, int currentOffset) {
1836 var lastLine =
1837 lineInfo.getLocation(lastOffset).lineNumber;
1838 var currentLine =
1839 lineInfo.getLocation(currentOffset).lineNumber;
1840 return currentLine - lastLine;
1841 }
1842
1843 String toString() => writer.toString();
1844
1845 @override
1846 visitEnumConstantDeclaration(EnumConstantDeclaration node) {
1847 // TODO: implement visitEnumConstantDeclaration
1848 }
1849
1850 @override
1851 visitEnumDeclaration(EnumDeclaration node) {
1852 // TODO: implement visitEnumDeclaration
1853 }
1854 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698