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

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

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

Powered by Google App Engine
This is Rietveld 408576698