Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 } | |
| OLD | NEW |