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

Side by Side Diff: pkg/analyzer/lib/src/services/formatter_impl.dart

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

Powered by Google App Engine
This is Rietveld 408576698