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