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

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

Issue 18346013: Formatter re-think/updates. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | pkg/analyzer_experimental/lib/src/services/writer.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library formatter_impl; 5 library formatter_impl;
6 6
7 7
8 import 'dart:io'; 8 import 'dart:io';
9 9
10 import 'package:analyzer_experimental/analyzer.dart'; 10 import 'package:analyzer_experimental/analyzer.dart';
11 import 'package:analyzer_experimental/src/generated/parser.dart'; 11 import 'package:analyzer_experimental/src/generated/parser.dart';
12 import 'package:analyzer_experimental/src/generated/scanner.dart'; 12 import 'package:analyzer_experimental/src/generated/scanner.dart';
13 import 'package:analyzer_experimental/src/generated/source.dart'; 13 import 'package:analyzer_experimental/src/generated/source.dart';
14 14 import 'package:analyzer_experimental/src/services/writer.dart';
15 15
16 /// OS line separator. --- TODO(pquitslund): may not be necessary 16 /// OS line separator. --- TODO(pquitslund): may not be necessary
17 const NEW_LINE = '\n' ; //Platform.pathSeparator; 17 const NEW_LINE = '\n' ; //Platform.pathSeparator;
18 18
19 /// Formatter options. 19 /// Formatter options.
20 class FormatterOptions { 20 class FormatterOptions {
21 21
22 /// Create formatter options with defaults derived (where defined) from 22 /// Create formatter options with defaults derived (where defined) from
23 /// the style guide: <http://www.dartlang.org/articles/style-guide/>. 23 /// the style guide: <http://www.dartlang.org/articles/style-guide/>.
24 const FormatterOptions({this.initialIndentationLevel: 0, 24 const FormatterOptions({this.initialIndentationLevel: 0,
25 this.indentPerLevel: 2, 25 this.spacesPerIndent: 2,
26 this.lineSeparator: NEW_LINE, 26 this.lineSeparator: NEW_LINE,
27 this.pageWidth: 80, 27 this.pageWidth: 80,
28 this.tabsForIndent: false,
28 this.tabSize: 2}); 29 this.tabSize: 2});
29 30
30 final String lineSeparator; 31 final String lineSeparator;
31 final int initialIndentationLevel; 32 final int initialIndentationLevel;
32 final int indentPerLevel; 33 final int spacesPerIndent;
33 final int tabSize; 34 final int tabSize;
35 final bool tabsForIndent;
34 final int pageWidth; 36 final int pageWidth;
35 } 37 }
36 38
37 39
38 /// Thrown when an error occurs in formatting. 40 /// Thrown when an error occurs in formatting.
39 class FormatterException implements Exception { 41 class FormatterException implements Exception {
40 42
41 /// A message describing the error. 43 /// A message describing the error.
42 final message; 44 final message;
43 45
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
76 /// Format the specified portion (from [offset] with [length]) of the given 78 /// Format the specified portion (from [offset] with [length]) of the given
77 /// [source] string, optionally providing an [indentationLevel]. 79 /// [source] string, optionally providing an [indentationLevel].
78 String format(CodeKind kind, String source, {int offset, int end, 80 String format(CodeKind kind, String source, {int offset, int end,
79 int indentationLevel:0}); 81 int indentationLevel:0});
80 82
81 } 83 }
82 84
83 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { 85 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener {
84 86
85 final FormatterOptions options; 87 final FormatterOptions options;
86 final EditRecorder recorder;
87 final errors = <AnalysisError>[]; 88 final errors = <AnalysisError>[];
88 89
89 CodeFormatterImpl(FormatterOptions options) : this.options = options, 90 CodeFormatterImpl(this.options);
90 recorder = new EditRecorder(options);
91 91
92 String format(CodeKind kind, String source, {int offset, int end, 92 String format(CodeKind kind, String source, {int offset, int end,
93 int indentationLevel:0}) { 93 int indentationLevel:0}) {
94 94
95 var start = tokenize(source); 95 var start = tokenize(source);
96 checkForErrors(); 96 checkForErrors();
97 97
98 var node = parse(kind, start); 98 var node = parse(kind, start);
99 checkForErrors(); 99 checkForErrors();
100 100
101 var formatter = new FormattingEngine(options); 101 var formatter = new SourceVisitor(options);
102 return formatter.format(source, node, start, kind, recorder); 102 node.accept(formatter);
103
104 return formatter.writer.toString();
103 } 105 }
104 106
105 ASTNode parse(CodeKind kind, Token start) { 107 ASTNode parse(CodeKind kind, Token start) {
106 108
107 var parser = new Parser(null, this); 109 var parser = new Parser(null, this);
108 110
109 switch (kind) { 111 switch (kind) {
110 case CodeKind.COMPILATION_UNIT: 112 case CodeKind.COMPILATION_UNIT:
111 return parser.parseCompilationUnit(start); 113 return parser.parseCompilationUnit(start);
112 case CodeKind.STATEMENT: 114 case CodeKind.STATEMENT:
(...skipping 14 matching lines...) Expand all
127 } 129 }
128 130
129 Token tokenize(String source) { 131 Token tokenize(String source) {
130 var scanner = new StringScanner(null, source, this); 132 var scanner = new StringScanner(null, source, this);
131 return scanner.tokenize(); 133 return scanner.tokenize();
132 } 134 }
133 135
134 } 136 }
135 137
136 138
137 /// Records a sequence of edits to a source string that will cause the string 139
138 /// to be formatted when applied. 140 /// An AST visitor that drives formatting heuristics.
139 class EditRecorder { 141 class SourceVisitor implements ASTVisitor {
140 142
141 final FormatterOptions options; 143 /// The writer to which the source is to be written.
142 final EditStore editStore; 144 SourceWriter writer;
143 145
144 int column = 0; 146 /// Initialize a newly created visitor to write source code representing
145 147 /// the visited nodes to the given [writer].
146 int sourceIndex = 0; 148 SourceVisitor(FormatterOptions options) :
147 String source = ''; 149 writer = new SourceWriter(initialIndent: options.initialIndentationLevel,
Brian Wilkerson 2013/07/03 22:03:02 This seems weird. Why not just pass in the options
pquitslund 2013/07/03 22:21:39 I was on the fence. Leaning towards doing just th
148 150 lineSeparator: options.lineSeparator);
149 Token currentToken; 151
150 152 visitAdjacentStrings(AdjacentStrings node) {
151 int numberOfIndentations = 0; 153 visitList(node.strings, ' ');
152 154 }
153 bool needsIndent = false; 155
154 156 visitAnnotation(Annotation node) {
155 EditRecorder(this.options): editStore = new EditStore(); 157 writer.print('@');
156 158 visit(node.name);
157 /// Add an [Edit] that describes a textual [replacement] of a text 159 visitPrefixed('.', node.constructorName);
158 /// interval starting at the given [offset] spanning the given [length]. 160 visit(node.arguments);
159 void addEdit(int offset, int length, String replacement) { 161 }
160 editStore.addEdit(offset, length, replacement); 162
161 } 163 visitArgumentDefinitionTest(ArgumentDefinitionTest node) {
162 164 writer.print('?');
163 /// Advance past the given expected [token] (or fail if not matched). 165 visit(node.identifier);
164 void advance(Token token) { 166 }
165 if (currentToken.lexeme == token.lexeme) { 167
166 168 visitArgumentList(ArgumentList node) {
167 // TODO(pquitslund) emit comments 169 writer.print('(');
168 // if (needsIndent) { 170 visitList(node.arguments, ', ');
169 // advanceIndent(); 171 writer.print(')');
170 // needsIndent = false; 172 }
171 // } 173
172 // Record writing a token at the current edit location 174 visitAsExpression(AsExpression node) {
173 advanceChars(token.length); 175 visit(node.expression);
174 currentToken = currentToken.next; 176 writer.print(' as ');
177 visit(node.type);
178 }
179
180 visitAssertStatement(AssertStatement node) {
181 writer.print('assert (');
182 visit(node.condition);
183 writer.print(');');
184 }
185
186 visitAssignmentExpression(AssignmentExpression node) {
187 visit(node.leftHandSide);
188 writer.print(' ');
189 writer.print(node.operator.lexeme);
190 writer.print(' ');
191 visit(node.rightHandSide);
192 }
193
194 visitBinaryExpression(BinaryExpression node) {
195 visit(node.leftOperand);
196 writer.print(' ');
197 writer.print(node.operator.lexeme);
198 writer.print(' ');
199 visit(node.rightOperand);
200 }
201
202 visitBlock(Block node) {
203 writer.print('{');
204 writer.indent();
205
206 for (var stmt in node.statements) {
207 writer.newline();
208 visit(stmt);
209 }
210
211 writer.unindent();
212 writer.newline();
213 writer.print('}');
214 }
215
216 visitBlockFunctionBody(BlockFunctionBody node) {
217 visit(node.block);
218 }
219
220 visitBooleanLiteral(BooleanLiteral node) {
221 writer.print(node.literal.lexeme);
222 }
223
224 visitBreakStatement(BreakStatement node) {
225 writer.print('break');
226 visitPrefixed(' ', node.label);
227 writer.print(';');
228 }
229
230 visitCascadeExpression(CascadeExpression node) {
231 visit(node.target);
232 visitList(node.cascadeSections);
233 }
234
235 visitCatchClause(CatchClause node) {
236 visitPrefixed('on ', node.exceptionType);
237 if (node.catchKeyword != null) {
238 if (node.exceptionType != null) {
239 writer.print(' ');
240 }
241 writer.print('catch (');
242 visit(node.exceptionParameter);
243 visitPrefixed(', ', node.stackTraceParameter);
244 writer.print(') ');
175 } else { 245 } else {
176 wrongToken(token.lexeme); 246 writer.print(' ');
177 } 247 }
178 } 248 visit(node.body);
179 249 }
180 /// Move indices past indent, adding an edit if needed to adjust indentation 250
181 void advanceIndent() { 251 visitClassDeclaration(ClassDeclaration node) {
182 // var indentWidth = options.indentPerLevel * indentationLevel; 252 visitToken(node.abstractKeyword, ' ');
183 // var indentString = getIndentString(indentWidth); 253 writer.print('class ');
184 // var sourceIndentWidth = 0; 254 visit(node.name);
185 // for (var i = 0; i < source.length; i++) { 255 visit(node.typeParameters);
186 // if (isIndentChar(source[sourceIndex + i])) { 256 visitPrefixed(' ', node.extendsClause);
187 // sourceIndentWidth += 1; 257 visitPrefixed(' ', node.withClause);
188 // } else { 258 visitPrefixed(' ', node.implementsClause);
189 // break; 259 writer.print(' {');
190 // } 260 writer.indent();
191 // } 261 for (var member in node.members) {
192 // var hasSameIndent = sourceIndentWidth == indentWidth; 262 writer.newline();
193 // if (hasSameIndent) { 263 visit(member);
194 // for (var i = 0; i < indentWidth; i++) { 264 }
195 // if (source[sourceIndex + i] != indentString[i]) { 265
196 // hasSameIndent = false; 266 writer.unindent();
197 // break; 267 writer.newline();
198 // } 268 writer.print('}');
199 // } 269 }
200 // if (hasSameIndent) { 270
201 // advanceChars(indentWidth); 271 visitClassTypeAlias(ClassTypeAlias node) {
202 // return; 272 writer.print('typedef ');
203 // } 273 visit(node.name);
204 // } 274 visit(node.typeParameters);
205 // addEdit(sourceIndex, sourceIndentWidth, indentString); 275 writer.print(' = ');
206 // column += indentWidth; 276 if (node.abstractKeyword != null) {
207 // sourceIndex += sourceIndentWidth; 277 writer.print('abstract ');
208 278 }
209 var indent = options.indentPerLevel * numberOfIndentations; 279 visit(node.superclass);
210 280 visitPrefixed(' ', node.withClause);
211 spaces(indent); 281 visitPrefixed(' ', node.implementsClause);
212 } 282 writer.print(';');
213 283 }
214 String getIndentString(int indentWidth) { 284
215 285 visitComment(Comment node) => null;
216 // TODO(pquitslund) a temporary workaround 286
217 if (indentWidth < 0) { 287 visitCommentReference(CommentReference node) => null;
218 return ''; 288
219 } 289 visitCompilationUnit(CompilationUnit node) {
220 290 var scriptTag = node.scriptTag;
221 // TODO(pquitslund) allow indent with tab chars 291 var directives = node.directives;
222 292 visit(scriptTag);
223 // Fetch a precomputed indent string 293 var prefix = scriptTag == null ? '' : ' ';
224 if (indentWidth < SPACES.length) { 294 visitPrefixedList(prefix, directives, ' ');
225 return SPACES[indentWidth]; 295 prefix = scriptTag == null && directives.isEmpty ? '' : ' ';
226 } 296 visitPrefixedList(prefix, node.declarations, ' ');
227 297 }
228 // Build un-precomputed strings dynamically 298
229 var sb = new StringBuffer(); 299 visitConditionalExpression(ConditionalExpression node) {
230 for (var i = 0; i < indentWidth; ++i) { 300 visit(node.condition);
231 sb.write(' '); 301 writer.print(' ? ');
232 } 302 visit(node.thenExpression);
233 return sb.toString(); 303 writer.print(' : ');
234 } 304 visit(node.elseExpression);
235 305 }
236 /// Advance past the given expected [token] (or fail if not matched). 306
237 void advanceToken(String token) { 307 visitConstructorDeclaration(ConstructorDeclaration node) {
238 if (currentToken.lexeme == token) { 308 visitToken(node.externalKeyword, ' ');
239 advance(currentToken); 309 visitToken(node.constKeyword, ' ');
310 visitToken(node.factoryKeyword, ' ');
311 visit(node.returnType);
312 visitPrefixed('.', node.name);
313 visit(node.parameters);
314 visitPrefixedList(' : ', node.initializers, ', ');
315 visitPrefixed(' = ', node.redirectedConstructor);
316 visitPrefixedBody(' ', node.body);
317 }
318
319 visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
320 visitToken(node.keyword, '.');
321 visit(node.fieldName);
322 writer.print(' = ');
323 visit(node.expression);
324 }
325
326 visitConstructorName(ConstructorName node) {
327 visit(node.type);
328 visitPrefixed('.', node.name);
329 }
330
331 visitContinueStatement(ContinueStatement node) {
332 writer.print('continue');
333 visitPrefixed(' ', node.label);
334 writer.print(';');
335 }
336
337 visitDeclaredIdentifier(DeclaredIdentifier node) {
338 visitToken(node.keyword, ' ');
339 visitSuffixed(node.type, ' ');
340 visit(node.identifier);
341 }
342
343 visitDefaultFormalParameter(DefaultFormalParameter node) {
344 visit(node.parameter);
345 if (node.separator != null) {
346 writer.print(' ');
347 writer.print(node.separator.lexeme);
348 visitPrefixed(' ', node.defaultValue);
349 }
350 }
351
352 visitDoStatement(DoStatement node) {
353 writer.print('do ');
354 visit(node.body);
355 writer.print(' while (');
356 visit(node.condition);
357 writer.print(');');
358 }
359
360 visitDoubleLiteral(DoubleLiteral node) {
361 writer.print(node.literal.lexeme);
362 }
363
364 visitEmptyFunctionBody(EmptyFunctionBody node) {
365 writer.print(';');
366 }
367
368 visitEmptyStatement(EmptyStatement node) {
369 writer.print(';');
370 }
371
372 visitExportDirective(ExportDirective node) {
373 writer.print('export ');
374 visit(node.uri);
375 visitPrefixedList(' ', node.combinators, ' ');
376 writer.print(';');
377 }
378
379 visitExpressionFunctionBody(ExpressionFunctionBody node) {
380 writer.print('=> ');
381 visit(node.expression);
382 if (node.semicolon != null) {
383 writer.print(';');
384 }
385 }
386
387 visitExpressionStatement(ExpressionStatement node) {
388 visit(node.expression);
389 writer.print(';');
390 }
391
392 visitExtendsClause(ExtendsClause node) {
393 writer.print('extends ');
394 visit(node.superclass);
395 }
396
397 visitFieldDeclaration(FieldDeclaration node) {
398 visitToken(node.keyword, ' ');
399 visit(node.fields);
400 writer.print(';');
401 }
402
403 visitFieldFormalParameter(FieldFormalParameter node) {
404 visitToken(node.keyword, ' ');
405 visitSuffixed(node.type, ' ');
406 writer.print('this.');
407 visit(node.identifier);
408 visit(node.parameters);
409 }
410
411 visitForEachStatement(ForEachStatement node) {
412 writer.print('for (');
413 visit(node.loopVariable);
414 writer.print(' in ');
415 visit(node.iterator);
416 writer.print(') ');
417 visit(node.body);
418 }
419
420 visitFormalParameterList(FormalParameterList node) {
421 var groupEnd = null;
422 writer.print('(');
423 var parameters = node.parameters;
424 var size = parameters.length;
425 for (var i = 0; i < size; i++) {
426 var parameter = parameters[i];
427 if (i > 0) {
428 writer.print(', ');
429 }
430 if (groupEnd == null && parameter is DefaultFormalParameter) {
431 if (identical(parameter.kind, ParameterKind.NAMED)) {
432 groupEnd = '}';
433 writer.print('{');
434 } else {
435 groupEnd = ']';
436 writer.print('[');
437 }
438 }
439 parameter.accept(this);
440 }
441 if (groupEnd != null) {
442 writer.print(groupEnd);
443 }
444 writer.print(')');
445 }
446
447 visitForStatement(ForStatement node) {
448 var initialization = node.initialization;
449 writer.print('for (');
450 if (initialization != null) {
451 visit(initialization);
240 } else { 452 } else {
241 wrongToken(token); 453 visit(node.variables);
242 } 454 }
243 } 455 writer.print(';');
244 456 visitPrefixed(' ', node.condition);
245 /// Advance [column] and [sourceIndex] indices by [len] characters. 457 writer.print(';');
246 void advanceChars(int len) { 458 visitPrefixedList(' ', node.updaters, ', ');
247 column += len; 459 writer.print(') ');
248 sourceIndex += len; 460 visit(node.body);
249 } 461 }
250 462
251 /// Count the number of whitespace chars beginning at the current 463 visitFunctionDeclaration(FunctionDeclaration node) {
252 /// [sourceIndex]. 464 visitSuffixed(node.returnType, ' ');
253 int countWhitespace() { 465 visitToken(node.propertyKeyword, ' ');
254 var count = 0; 466 visit(node.name);
255 for (var i = sourceIndex; i < source.length; ++i) { 467 visit(node.functionExpression);
256 if (isIndentChar(source[i])) { 468 }
257 ++count; 469
258 } else { 470 visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
259 break; 471 visit(node.functionDeclaration);
472 writer.print(';');
473 }
474
475 visitFunctionExpression(FunctionExpression node) {
476 visit(node.parameters);
477 writer.print(' ');
478 visit(node.body);
479 }
480
481 visitFunctionExpressionInvocation(FunctionExpressionInvocation node) {
482 visit(node.function);
483 visit(node.argumentList);
484 }
485
486 visitFunctionTypeAlias(FunctionTypeAlias node) {
487 writer.print('typedef ');
488 visitSuffixed(node.returnType, ' ');
489 visit(node.name);
490 visit(node.typeParameters);
491 visit(node.parameters);
492 writer.print(';');
493 }
494
495 visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
496 visitSuffixed(node.returnType, ' ');
497 visit(node.identifier);
498 visit(node.parameters);
499 }
500
501 visitHideCombinator(HideCombinator node) {
502 writer.print('hide ');
503 visitList(node.hiddenNames, ', ');
504 }
505
506 visitIfStatement(IfStatement node) {
507 writer.print('if (');
508 visit(node.condition);
509 writer.print(') ');
510 visit(node.thenStatement);
511 visitPrefixed(' else ', node.elseStatement);
512 }
513
514 visitImplementsClause(ImplementsClause node) {
515 writer.print('implements ');
516 visitList(node.interfaces, ', ');
517 }
518
519 visitImportDirective(ImportDirective node) {
520 writer.print('import ');
521 visit(node.uri);
522 visitPrefixed(' as ', node.prefix);
523 visitPrefixedList(' ', node.combinators, ' ');
524 writer.print(';');
525 }
526
527 visitIndexExpression(IndexExpression node) {
528 if (node.isCascaded) {
529 writer.print('..');
530 } else {
531 visit(node.array);
532 }
533 writer.print('[');
534 visit(node.index);
535 writer.print(']');
536 }
537
538 visitInstanceCreationExpression(InstanceCreationExpression node) {
539 visitToken(node.keyword, ' ');
540 visit(node.constructorName);
541 visit(node.argumentList);
542 }
543
544 visitIntegerLiteral(IntegerLiteral node) {
545 writer.print(node.literal.lexeme);
546 }
547
548 visitInterpolationExpression(InterpolationExpression node) {
549 if (node.rightBracket != null) {
550 writer.print('\${');
551 visit(node.expression);
552 writer.print('}');
553 } else {
554 writer.print('\$');
555 visit(node.expression);
556 }
557 }
558
559 visitInterpolationString(InterpolationString node) {
560 writer.print(node.contents.lexeme);
561 }
562
563 visitIsExpression(IsExpression node) {
564 visit(node.expression);
565 if (node.notOperator == null) {
566 writer.print(' is ');
567 } else {
568 writer.print(' is! ');
569 }
570 visit(node.type);
571 }
572
573 visitLabel(Label node) {
574 visit(node.label);
575 writer.print(':');
576 }
577
578 visitLabeledStatement(LabeledStatement node) {
579 visitSuffixedList(node.labels, ' ', ' ');
580 visit(node.statement);
581 }
582
583 visitLibraryDirective(LibraryDirective node) {
584 writer.print('library ');
585 visit(node.name);
586 writer.print(';');
587 }
588
589 visitLibraryIdentifier(LibraryIdentifier node) {
590 writer.print(node.name);
591 }
592
593 visitListLiteral(ListLiteral node) {
594 if (node.modifier != null) {
595 writer.print(node.modifier.lexeme);
596 writer.print(' ');
597 }
598 visitSuffixed(node.typeArguments, ' ');
599 writer.print('[');
600 visitList(node.elements, ', ');
601 writer.print(']');
602 }
603
604 visitMapLiteral(MapLiteral node) {
605 if (node.modifier != null) {
606 writer.print(node.modifier.lexeme);
607 writer.print(' ');
608 }
609 visitSuffixed(node.typeArguments, ' ');
610 writer.print('{');
611 visitList(node.entries, ', ');
612 writer.print('}');
613 }
614
615 visitMapLiteralEntry(MapLiteralEntry node) {
616 visit(node.key);
617 writer.print(' : ');
618 visit(node.value);
619 }
620
621 visitMethodDeclaration(MethodDeclaration node) {
622 visitToken(node.externalKeyword, ' ');
623 visitToken(node.modifierKeyword, ' ');
624 visitSuffixed(node.returnType, ' ');
625 visitToken(node.propertyKeyword, ' ');
626 visitToken(node.operatorKeyword, ' ');
627 visit(node.name);
628 if (!node.isGetter) {
629 visit(node.parameters);
630 }
631 visitPrefixedBody(' ', node.body);
632 }
633
634 visitMethodInvocation(MethodInvocation node) {
635 if (node.isCascaded) {
636 writer.print('..');
637 } else {
638 visitSuffixed(node.target, '.');
639 }
640 visit(node.methodName);
641 visit(node.argumentList);
642 }
643
644 visitNamedExpression(NamedExpression node) {
645 visit(node.name);
646 visitPrefixed(' ', node.expression);
647 }
648
649 visitNativeFunctionBody(NativeFunctionBody node) {
650 writer.print('native ');
651 visit(node.stringLiteral);
652 writer.print(';');
653 }
654
655 visitNullLiteral(NullLiteral node) {
656 writer.print('null');
657 }
658
659 visitParenthesizedExpression(ParenthesizedExpression node) {
660 writer.print('(');
661 visit(node.expression);
662 writer.print(')');
663 }
664
665 visitPartDirective(PartDirective node) {
666 writer.print('part ');
667 visit(node.uri);
668 writer.print(';');
669 }
670
671 visitPartOfDirective(PartOfDirective node) {
672 writer.print('part of ');
673 visit(node.libraryName);
674 writer.print(';');
675 }
676
677 visitPostfixExpression(PostfixExpression node) {
678 visit(node.operand);
679 writer.print(node.operator.lexeme);
680 }
681
682 visitPrefixedIdentifier(PrefixedIdentifier node) {
683 visit(node.prefix);
684 writer.print('.');
685 visit(node.identifier);
686 }
687
688 visitPrefixExpression(PrefixExpression node) {
689 writer.print(node.operator.lexeme);
690 visit(node.operand);
691 }
692
693 visitPropertyAccess(PropertyAccess node) {
694 if (node.isCascaded) {
695 writer.print('..');
696 } else {
697 visit(node.target);
698 writer.print('.');
699 }
700 visit(node.propertyName);
701 }
702
703 visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
704 writer.print('this');
705 visitPrefixed('.', node.constructorName);
706 visit(node.argumentList);
707 }
708
709 visitRethrowExpression(RethrowExpression node) {
710 writer.print('rethrow');
711 }
712
713 visitReturnStatement(ReturnStatement node) {
714 var expression = node.expression;
715 if (expression == null) {
716 writer.print('return;');
717 } else {
718 writer.print('return ');
719 expression.accept(this);
720 writer.print(';');
721 }
722 }
723
724 visitScriptTag(ScriptTag node) {
725 writer.print(node.scriptTag.lexeme);
726 }
727
728 visitShowCombinator(ShowCombinator node) {
729 writer.print('show ');
730 visitList(node.shownNames, ', ');
731 }
732
733 visitSimpleFormalParameter(SimpleFormalParameter node) {
734 visitToken(node.keyword, ' ');
735 visitSuffixed(node.type, ' ');
736 visit(node.identifier);
737 }
738
739 visitSimpleIdentifier(SimpleIdentifier node) {
740 writer.print(node.token.lexeme);
741 }
742
743 visitSimpleStringLiteral(SimpleStringLiteral node) {
744 writer.print(node.literal.lexeme);
745 }
746
747 visitStringInterpolation(StringInterpolation node) {
748 visitList(node.elements);
749 }
750
751 visitSuperConstructorInvocation(SuperConstructorInvocation node) {
752 writer.print('super');
753 visitPrefixed('.', node.constructorName);
754 visit(node.argumentList);
755 }
756
757 visitSuperExpression(SuperExpression node) {
758 writer.print('super');
759 }
760
761 visitSwitchCase(SwitchCase node) {
762 visitSuffixedList(node.labels, ' ', ' ');
763 writer.print('case ');
764 visit(node.expression);
765 writer.print(': ');
766 visitList(node.statements, ' ');
767 }
768
769 visitSwitchDefault(SwitchDefault node) {
770 visitSuffixedList(node.labels, ' ', ' ');
771 writer.print('default: ');
772 visitList(node.statements, ' ');
773 }
774
775 visitSwitchStatement(SwitchStatement node) {
776 writer.print('switch (');
777 visit(node.expression);
778 writer.print(') {');
779 visitList(node.members, ' ');
780 writer.print('}');
781 }
782
783 visitSymbolLiteral(SymbolLiteral node) {
784 // No-op ?
785 }
786
787 visitThisExpression(ThisExpression node) {
788 writer.print('this');
789 }
790
791 visitThrowExpression(ThrowExpression node) {
792 writer.print('throw ');
793 visit(node.expression);
794 }
795
796 visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
797 visitSuffixed(node.variables, ';');
798 }
799
800 visitTryStatement(TryStatement node) {
801 writer.print('try ');
802 visit(node.body);
803 visitPrefixedList(' ', node.catchClauses, ' ');
804 visitPrefixed(' finally ', node.finallyClause);
805 }
806
807 visitTypeArgumentList(TypeArgumentList node) {
808 writer.print('<');
809 visitList(node.arguments, ', ');
810 writer.print('>');
811 }
812
813 visitTypeName(TypeName node) {
814 visit(node.name);
815 visit(node.typeArguments);
816 }
817
818 visitTypeParameter(TypeParameter node) {
819 visit(node.name);
820 visitPrefixed(' extends ', node.bound);
821 }
822
823 visitTypeParameterList(TypeParameterList node) {
824 writer.print('<');
825 visitList(node.typeParameters, ', ');
826 writer.print('>');
827 }
828
829 visitVariableDeclaration(VariableDeclaration node) {
830 visit(node.name);
831 visitPrefixed(' = ', node.initializer);
832 }
833
834 visitVariableDeclarationList(VariableDeclarationList node) {
835 visitToken(node.keyword, ' ');
836 visitSuffixed(node.type, ' ');
837 visitList(node.variables, ', ');
838 }
839
840 visitVariableDeclarationStatement(VariableDeclarationStatement node) {
841 visit(node.variables);
842 writer.print(';');
843 }
844
845 visitWhileStatement(WhileStatement node) {
846 writer.print('while (');
847 visit(node.condition);
848 writer.print(') ');
849 visit(node.body);
850 }
851
852 visitWithClause(WithClause node) {
853 writer.print('with ');
854 visitList(node.mixinTypes, ', ');
855 }
856
857 /// Safely visit the given [node].
858 visit(ASTNode node) {
859 if (node != null) {
860 node.accept(this);
861 }
862 }
863
864 /// Safely visit the given [node], printing the [suffix] after the node if it
865 /// is non-null.
866 visitSuffixed(ASTNode node, String suffix) {
867 if (node != null) {
868 node.accept(this);
869 writer.print(suffix);
870 }
871 }
872
873 /// Safely visit the given [node], printing the [prefix] before the node if
874 /// it is non-null.
875 visitPrefixed(String prefix, ASTNode node) {
876 if (node != null) {
877 writer.print(prefix);
878 node.accept(this);
879 }
880 }
881
882 /// Visit the given function [body], printing the [prefix] before if given
883 /// body is not empty.
884 visitPrefixedBody(String prefix, FunctionBody body) {
885 if (body is! EmptyFunctionBody) {
886 writer.print(prefix);
887 }
888 visit(body);
889 }
890
891 /// Safely visit the given [token], printing the suffix after the [token]
892 /// node if it is non-null.
893 visitToken(Token token, String suffix) {
894 if (token != null) {
895 writer.print(token.lexeme);
896 writer.print(suffix);
897 }
898 }
899
900 /// Print a list of [nodes], separated by the given [separator].
901 visitList(NodeList<ASTNode> nodes, [String separator = '']) {
902 if (nodes != null) {
903 var size = nodes.length;
904 for (var i = 0; i < size; i++) {
905 if (i > 0) {
906 writer.print(separator);
907 }
908 nodes[i].accept(this);
260 } 909 }
261 } 910 }
262 return count; 911 }
263 } 912
264 913 /// Print a list of [nodes], separated by the given [separator].
265 /// Update indent indices. 914 visitSuffixedList(NodeList<ASTNode> nodes, String separator, String suffix) {
266 void indent() { 915 if (nodes != null) {
267 numberOfIndentations++; 916 var size = nodes.length;
268 } 917 if (size > 0) {
269 918 for (var i = 0; i < size; i++) {
270 /// Test if there is a newline at the given source [index]. 919 if (i > 0) {
271 bool isNewlineAt(int index) { 920 writer.print(separator);
272 if (index < 0 || index + NEW_LINE.length > source.length) { 921 }
273 return false; 922 nodes[i].accept(this);
274 } 923 }
275 for (var i = 0; i < NEW_LINE.length; i++) { 924 writer.print(suffix);
276 if (source[index] != NEW_LINE[i]) {
277 return false;
278 } 925 }
279 } 926 }
280 return true; 927 }
281 } 928
282 929 /// Print a list of [nodes], separated by the given [separator].
283 /// Newline. 930 visitPrefixedList(String prefix, NodeList<ASTNode> nodes, String separator) {
284 void newline() { 931 if (nodes != null) {
285 // TODO(pquitslund) emit comments 932 var size = nodes.length;
286 needsIndent = true; 933 if (size > 0) {
287 // If there is a newline before the edit location, do nothing. 934 writer.print(prefix);
288 if (isNewlineAt(sourceIndex - NEW_LINE.length)) { 935 for (var i = 0; i < size; i++) {
289 return; 936 if (i > 0) {
290 } 937 writer.print(separator);
291 // If there is a newline after the edit location, advance over it. 938 }
292 if (isNewlineAt(sourceIndex)) { 939 nodes[i].accept(this);
293 advanceChars(NEW_LINE.length); 940 }
294 return; 941 }
295 } 942 }
296 // Otherwise, replace whitespace with a newline. 943 }
297 var charsToReplace = countWhitespace(); 944
298 if (isNewlineAt(sourceIndex + charsToReplace)) { 945 }
299 charsToReplace += NEW_LINE.length;
300 }
301 addEdit(sourceIndex, charsToReplace, NEW_LINE);
302 advanceChars(charsToReplace);
303 }
304
305
306 /// Un-indent.
307 void unindent() {
308 numberOfIndentations--;
309 }
310
311 /// Space.
312 void space() {
313 // TODO(pquitslund) emit comments
314 // // If there is a space before the edit location, do nothing.
315 // if (isSpaceAt(sourceIndex - 1)) {
316 // return;
317 // }
318 // // If there is a space after the edit location, advance over it.
319 // if (isSpaceAt(sourceIndex)) {
320 // advance(1);
321 // return;
322 // }
323 // Otherwise, replace spaces with a single space.
324 spaces(1);
325 }
326
327 /// Spaces.
328 void spaces(int num) {
329 var charsToReplace = countWhitespace();
330 addEdit(sourceIndex, charsToReplace, SPACES[num]);
331 advanceChars(charsToReplace);
332 }
333
334 wrongToken(String token) {
335 throw new FormatterException('expected token: "${token}", '
336 'actual: "${currentToken}"');
337 }
338
339 String toString() =>
340 new EditOperation().apply(editStore.edits,
341 source.substring(0, sourceIndex));
342
343 }
344
345 const SPACE = ' ';
346 final SPACES = [
347 '',
348 ' ',
349 ' ',
350 ' ',
351 ' ',
352 ' ',
353 ' ',
354 ' ',
355 ' ',
356 ' ',
357 ' ',
358 ' ',
359 ' ',
360 ' ',
361 ' ',
362 ' ',
363 ' ',
364 ];
365
366
367 bool isIndentChar(String ch) => ch == SPACE; // TODO(pquitslund) also check tab
368
369
370 /// Manages stored [Edit]s.
371 class EditStore {
372
373 const EditStore();
374
375 /// The underlying sequence of [Edit]s.
376 final edits = <Edit>[];
377
378 /// Add the given [Edit] to the end of the edit sequence.
379 void add(Edit edit) {
380 edits.add(edit);
381 }
382
383 /// Add an [Edit] that describes a textual [replacement] of a text interval
384 /// starting at the given [offset] spanning the given [length].
385 void addEdit(int offset, int length, String replacement) {
386 add(new Edit(offset, length, replacement));
387 }
388
389 /// Get the index of the current edit (for use in caching location
390 /// information).
391 int getCurrentEditIndex() => edits.length - 1;
392
393 /// Get the last edit.
394 Edit getLastEdit() => edits.isEmpty ? null : edits.last;
395
396 /// Add an [Edit] that describes an insertion of text starting at the given
397 /// [offset].
398 void insert(int offset, String insertedString) {
399 addEdit(offset, 0, insertedString);
400 }
401
402 /// Reset cached state.
403 void reset() {
404 edits.clear();
405 }
406
407 String toString() => 'EditStore( ${edits.toString()} )';
408
409 }
410
411
412 /// Describes a text edit.
413 class Edit {
414
415 /// The offset at which to apply the edit.
416 final int offset;
417
418 /// The length of the text interval to replace.
419 final int length;
420
421 /// The replacement text.
422 final String replacement;
423
424 /// Create an edit.
425 const Edit(this.offset, this.length, this.replacement);
426
427 /// Create an edit for the given [range].
428 Edit.forRange(SourceRange range, String replacement):
429 this(range.offset, range.length, replacement);
430
431 String toString() => '${offset < 0 ? '(' : 'X('} offset: ${offset} , '
432 'length ${length}, replacement :> ${replacement} <:)';
433
434 }
435
436 /// Applies a sequence of [edits] to a [document].
437 class EditOperation {
438
439 String apply(List<Edit> edits, String document) {
440
441 var edit;
442 for (var i = edits.length - 1; i >= 0; --i) {
443 edit = edits[i];
444 document = replace(document, edit.offset,
445 edit.offset + edit.length, edit.replacement);
446 }
447
448 return document;
449 }
450
451 }
452
453
454 String replace(String str, int start, int end, String replacement) =>
455 str.substring(0, start) + replacement + str.substring(end);
456
457
458 /// An AST visitor that drives formatting heuristics.
459 class FormattingEngine extends RecursiveASTVisitor {
460
461 final FormatterOptions options;
462
463 CodeKind kind;
464 EditRecorder recorder;
465
466 FormattingEngine(this.options);
467
468 String format(String source, ASTNode node, Token start, CodeKind kind,
469 EditRecorder recorder) {
470
471 this.kind = kind;
472 this.recorder = recorder;
473
474 recorder..source = source
475 ..currentToken = start;
476
477 node.accept(this);
478
479 var editor = new EditOperation();
480 return editor.apply(recorder.editStore.edits, source);
481 }
482
483
484 visitClassDeclaration(ClassDeclaration node) {
485
486 recorder.advanceIndent();
487
488 if (node.documentationComment != null) {
489 node.documentationComment.accept(this);
490 }
491
492 recorder..advance(node.classKeyword)..space();
493
494 node.name.accept(this);
495
496 if (node.typeParameters != null) {
497 node.typeParameters.accept(this);
498 }
499 recorder.space();
500
501 if (node.extendsClause != null) {
502 node.extendsClause.accept(this);
503 recorder.space();
504 }
505
506 if (node.implementsClause != null) {
507 node.implementsClause.accept(this);
508 recorder.space();
509 }
510
511 recorder..advance(node.leftBracket)
512 ..indent();
513
514 for (var member in node.members) {
515 recorder..newline()
516 ..advanceIndent();
517 member.accept(this);
518 }
519
520 recorder..unindent()
521 ..newline()
522 ..advanceIndent()
523 ..advance(node.rightBracket);
524 }
525
526
527 visitBlockFunctionBody(BlockFunctionBody node) {
528 node.block.accept(this);
529 }
530
531
532 visitBlock(Block block) {
533 recorder..advance(block.leftBracket)
534 ..indent()
535 ..newline();
536 // ...
537 recorder..unindent()
538 ..advanceIndent()
539 ..advance(block.rightBracket);
540 }
541
542
543 visitExpressionFunctionBody(ExpressionFunctionBody node) {
544 recorder..advance(node.functionDefinition)
545 ..indent()
546 ..newline();
547 node.expression.accept(this);
548 recorder..unindent()
549 ..advanceIndent()
550 ..advance(node.semicolon);
551 }
552
553
554 visitMethodDeclaration(MethodDeclaration node) {
555
556 if (node.modifierKeyword != null) {
557 recorder.advance(node.modifierKeyword);
558 recorder.space();
559 }
560
561 if (node.returnType != null) {
562 node.returnType.accept(this);
563 recorder.space();
564 }
565
566 recorder.advance(node.name.beginToken);
567
568 node.parameters.accept(this);
569
570 recorder.space();
571
572 node.body.accept(this);
573 }
574
575
576 visitFormalParameterList(FormalParameterList node) {
577 recorder.advance(node.beginToken);
578 //...
579 recorder.advance(node.endToken);
580 }
581
582
583 visitSimpleIdentifier(SimpleIdentifier node) {
584 recorder.advance(node.token);
585 }
586
587 }
OLDNEW
« no previous file with comments | « no previous file | pkg/analyzer_experimental/lib/src/services/writer.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698