OLD | NEW |
| (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 } | |
OLD | NEW |