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'; |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 76 /// Format the specified portion (from [offset] with [length]) of the given | 76 /// Format the specified portion (from [offset] with [length]) of the given |
| 77 /// [source] string, optionally providing an [indentationLevel]. | 77 /// [source] string, optionally providing an [indentationLevel]. |
| 78 String format(CodeKind kind, String source, {int offset, int end, | 78 String format(CodeKind kind, String source, {int offset, int end, |
| 79 int indentationLevel:0}); | 79 int indentationLevel:0}); |
| 80 | 80 |
| 81 } | 81 } |
| 82 | 82 |
| 83 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { | 83 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { |
| 84 | 84 |
| 85 final FormatterOptions options; | 85 final FormatterOptions options; |
| 86 final List<AnalysisError> errors = <AnalysisError>[]; | 86 final List<AnalysisError> errors = <AnalysisError>[]; |
|
scheglov
2013/06/19 22:12:05
Remove type annotation for the final field.
pquitslund
2013/06/20 22:34:52
Done.
| |
| 87 final EditRecorder recorder; | |
| 87 | 88 |
| 88 CodeFormatterImpl(this.options); | 89 CodeFormatterImpl(FormatterOptions options) : this.options = options, |
|
scheglov
2013/06/19 22:12:05
May be "this.options" and remove initializer?
pquitslund
2013/06/20 22:34:52
The trouble is that I'd like to use options in my
| |
| 90 recorder = new EditRecorder(options); | |
| 89 | 91 |
| 90 String format(CodeKind kind, String source, {int offset, int end, | 92 String format(CodeKind kind, String source, {int offset, int end, |
| 91 int indentationLevel:0}) { | 93 int indentationLevel:0}) { |
| 92 | 94 |
| 93 var start = tokenize(source); | 95 var start = tokenize(source); |
| 94 _checkForErrors(); | 96 checkForErrors(); |
| 95 | 97 |
| 96 var node = parse(kind, start); | 98 var node = parse(kind, start); |
| 97 _checkForErrors(); | 99 checkForErrors(); |
| 98 | 100 |
| 99 // To be continued... | 101 var formatter = new FormattingEngine(options); |
| 100 | 102 return formatter.format(source, node, start, kind, recorder); |
| 101 return source; | |
| 102 } | 103 } |
| 103 | 104 |
| 104 ASTNode parse(CodeKind kind, Token start) { | 105 ASTNode parse(CodeKind kind, Token start) { |
| 105 | 106 |
| 106 var parser = new Parser(null, this); | 107 var parser = new Parser(null, this); |
| 107 | 108 |
| 108 switch (kind) { | 109 switch (kind) { |
| 109 case CodeKind.COMPILATION_UNIT: | 110 case CodeKind.COMPILATION_UNIT: |
| 110 return parser.parseCompilationUnit(start); | 111 return parser.parseCompilationUnit(start); |
| 111 case CodeKind.STATEMENT: | 112 case CodeKind.STATEMENT: |
| 112 return parser.parseStatement(start); | 113 return parser.parseStatement(start); |
| 113 } | 114 } |
| 114 | 115 |
| 115 throw new FormatterException('Unsupported format kind: $kind'); | 116 throw new FormatterException('Unsupported format kind: $kind'); |
| 116 } | 117 } |
| 117 | 118 |
| 118 _checkForErrors() { | 119 checkForErrors() { |
| 119 if (errors.length > 0) { | 120 if (errors.length > 0) { |
| 120 throw new FormatterException.forError(errors); | 121 throw new FormatterException.forError(errors); |
| 121 } | 122 } |
| 122 } | 123 } |
| 123 | 124 |
| 124 void onError(AnalysisError error) { | 125 void onError(AnalysisError error) { |
| 125 errors.add(error); | 126 errors.add(error); |
| 126 } | 127 } |
| 127 | 128 |
| 128 Token tokenize(String source) { | 129 Token tokenize(String source) { |
| 129 var scanner = new StringScanner(null, source, this); | 130 var scanner = new StringScanner(null, source, this); |
| 130 return scanner.tokenize(); | 131 return scanner.tokenize(); |
| 131 } | 132 } |
| 132 | 133 |
| 133 } | 134 } |
| 134 | 135 |
| 135 /// Placeholder class to hold a reference to the Class object representing | |
| 136 /// the Dart keyword void. | |
| 137 class Void extends Object { | |
| 138 | |
| 139 } | |
| 140 | |
| 141 | 136 |
| 142 /// Records a sequence of edits to a source string that will cause the string | 137 /// Records a sequence of edits to a source string that will cause the string |
| 143 /// to be formatted when applied. | 138 /// to be formatted when applied. |
| 144 class EditRecorder { | 139 class EditRecorder { |
| 145 | 140 |
| 146 final FormatterOptions options; | 141 final FormatterOptions options; |
| 142 final EditStore editStore; | |
| 147 | 143 |
| 148 int column = 0; | 144 int column = 0; |
| 149 | 145 |
| 150 int sourceIndex = 0; | 146 int sourceIndex = 0; |
| 151 String source = ''; | 147 String source = ''; |
| 152 | 148 |
| 153 Token currentToken; | 149 Token currentToken; |
| 154 | 150 |
| 155 int indentationLevel = 0; | |
| 156 int numberOfIndentations = 0; | 151 int numberOfIndentations = 0; |
| 157 | 152 |
| 158 bool isIndentNeeded = false; | 153 bool needsIndent = false; |
| 159 | 154 |
| 160 EditRecorder(this.options); | 155 EditRecorder(this.options): editStore = new EditStore(); |
|
Brian Wilkerson
2013/06/19 21:50:52
Just curiosity, but why did you move away from usi
pquitslund
2013/06/20 22:34:52
Discussed (exhaustively) in person!
| |
| 156 | |
| 157 EditRecorder.forStore(this.options, this.editStore); | |
| 158 | |
| 159 /// Add an [Edit] that describes a textual [replacement] of a text | |
| 160 /// interval starting at the given [offset] spanning the given [length]. | |
| 161 void addEdit(int offset, int length, String replacement) { | |
| 162 editStore.addEdit(offset, length, replacement); | |
| 163 } | |
| 164 | |
| 165 /// Advance past the given expected [token] (or fail if not matched). | |
| 166 void advance(Token token) { | |
| 167 if (currentToken.lexeme == token.lexeme) { | |
| 168 | |
| 169 // TODO(pquitslund) emit comments | |
| 170 // if (needsIndent) { | |
| 171 // advanceIndent(); | |
| 172 // needsIndent = false; | |
| 173 // } | |
| 174 // Record writing a token at the current edit location | |
| 175 advanceChars(token.length); | |
| 176 currentToken = currentToken.next; | |
| 177 } else { | |
| 178 wrongToken(token.lexeme); | |
| 179 } | |
| 180 } | |
| 181 | |
| 182 /// Move indices past indent, adding an edit if needed to adjust indentation | |
| 183 void advanceIndent() { | |
| 184 // var indentWidth = options.indentPerLevel * indentationLevel; | |
| 185 // var indentString = getIndentString(indentWidth); | |
| 186 // var sourceIndentWidth = 0; | |
| 187 // for (var i = 0; i < source.length; i++) { | |
| 188 // if (isIndentChar(source[sourceIndex + i])) { | |
| 189 // sourceIndentWidth += 1; | |
| 190 // } else { | |
| 191 // break; | |
| 192 // } | |
| 193 // } | |
| 194 // var hasSameIndent = sourceIndentWidth == indentWidth; | |
| 195 // if (hasSameIndent) { | |
| 196 // for (var i = 0; i < indentWidth; i++) { | |
| 197 // if (source[sourceIndex + i] != indentString[i]) { | |
| 198 // hasSameIndent = false; | |
| 199 // break; | |
| 200 // } | |
| 201 // } | |
| 202 // if (hasSameIndent) { | |
| 203 // advanceChars(indentWidth); | |
| 204 // return; | |
| 205 // } | |
| 206 // } | |
| 207 // addEdit(sourceIndex, sourceIndentWidth, indentString); | |
| 208 // column += indentWidth; | |
| 209 // sourceIndex += sourceIndentWidth; | |
| 210 | |
| 211 var indent = options.indentPerLevel * numberOfIndentations; | |
| 212 | |
| 213 spaces(indent); | |
| 214 } | |
| 215 | |
| 216 String getIndentString(int indentWidth) { | |
| 217 | |
| 218 // TODO(pquitslund) a temporary workaround | |
| 219 if (indentWidth < 0) { | |
| 220 return ''; | |
| 221 } | |
| 222 | |
| 223 // TODO(pquitslund) allow indent with tab chars | |
| 224 | |
| 225 // Fetch a precomputed indent string | |
| 226 if (indentWidth < SPACES.length) { | |
| 227 return SPACES[indentWidth]; | |
| 228 } | |
| 229 | |
| 230 // Build un-precomputed strings dynamically | |
| 231 var sb = new StringBuffer(); | |
| 232 for (var i=0; i < indentWidth; ++i) { | |
|
scheglov
2013/06/19 22:12:05
Whitespaces before/after '='.
pquitslund
2013/06/20 22:34:52
Done.
| |
| 233 sb.write(' '); | |
| 234 } | |
| 235 return sb.toString(); | |
| 236 } | |
| 237 | |
| 238 /// Advance past the given expected [token] (or fail if not matched). | |
| 239 void advanceToken(String token) { | |
| 240 if (currentToken.lexeme == token) { | |
| 241 advance(currentToken); | |
| 242 } else { | |
| 243 wrongToken(token); | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 /// Advance [column] and [sourceIndex] indices by [len] characters. | |
| 248 void advanceChars(int len) { | |
| 249 column += len; | |
| 250 sourceIndex += len; | |
| 251 } | |
| 161 | 252 |
| 162 /// Count the number of whitespace chars beginning at the current | 253 /// Count the number of whitespace chars beginning at the current |
| 163 /// [sourceIndex]. | 254 /// [sourceIndex]. |
| 164 int countWhitespace() { | 255 int countWhitespace() { |
| 165 var count = 0; | 256 var count = 0; |
| 166 for (var i = sourceIndex; i < source.length; ++i) { | 257 for (var i = sourceIndex; i < source.length; ++i) { |
| 167 if (isIndentChar(source[i])) { | 258 if (isIndentChar(source[i])) { |
| 168 ++count; | 259 ++count; |
| 169 } else { | 260 } else { |
| 170 break; | 261 break; |
| 171 } | 262 } |
| 172 } | 263 } |
| 173 return count; | 264 return count; |
| 174 } | 265 } |
| 175 | 266 |
| 176 /// Indent. | 267 /// Update indent indices. |
| 177 void indent() { | 268 void indent() { |
| 178 indentationLevel += options.indentPerLevel; | |
| 179 numberOfIndentations++; | 269 numberOfIndentations++; |
| 180 } | 270 } |
| 181 | 271 |
| 182 /// Test if there is a newline at the given source [index]. | 272 /// Test if there is a newline at the given source [index]. |
| 183 bool isNewlineAt(int index) { | 273 bool isNewlineAt(int index) { |
| 184 if (index < 0 || index + NEW_LINE.length > source.length) { | 274 if (index < 0 || index + NEW_LINE.length > source.length) { |
| 185 return false; | 275 return false; |
| 186 } | 276 } |
| 187 for (var i = 0; i < NEW_LINE.length; i++) { | 277 for (var i = 0; i < NEW_LINE.length; i++) { |
| 188 if (source[index] != NEW_LINE[i]) { | 278 if (source[index] != NEW_LINE[i]) { |
| 189 return false; | 279 return false; |
| 190 } | 280 } |
| 191 } | 281 } |
| 192 return true; | 282 return true; |
| 193 } | 283 } |
| 194 | 284 |
| 285 /// Newline. | |
| 286 void newline() { | |
| 287 // TODO(pquitslund) emit comments | |
| 288 needsIndent = true; | |
| 289 // If there is a newline before the edit location, do nothing. | |
| 290 if (isNewlineAt(sourceIndex - NEW_LINE.length)) { | |
| 291 return; | |
| 292 } | |
| 293 // If there is a newline after the edit location, advance over it. | |
| 294 if (isNewlineAt(sourceIndex)) { | |
| 295 advanceChars(NEW_LINE.length); | |
| 296 return; | |
| 297 } | |
| 298 // Otherwise, replace whitespace with a newline. | |
| 299 var charsToReplace = countWhitespace(); | |
| 300 if (isNewlineAt(sourceIndex + charsToReplace)) { | |
| 301 ++charsToReplace; | |
|
Brian Wilkerson
2013/06/19 21:50:52
Does this want to be incremented by NEW_LINE.lengt
pquitslund
2013/06/20 22:34:52
Yes! Good catch. :)
| |
| 302 } | |
| 303 addEdit(sourceIndex, charsToReplace, NEW_LINE); | |
| 304 advanceChars(charsToReplace); | |
| 305 } | |
| 306 | |
| 307 | |
| 308 /// Un-indent. | |
| 309 void unindent() { | |
| 310 numberOfIndentations--; | |
| 311 } | |
| 312 | |
| 313 /// Space. | |
| 314 void space() { | |
| 315 // TODO(pquitslund) emit comments | |
| 316 // // If there is a space before the edit location, do nothing. | |
| 317 // if (isSpaceAt(sourceIndex - 1)) { | |
| 318 // return; | |
| 319 // } | |
| 320 // // If there is a space after the edit location, advance over it. | |
| 321 // if (isSpaceAt(sourceIndex)) { | |
| 322 // advance(1); | |
| 323 // return; | |
| 324 // } | |
| 325 // Otherwise, replace spaces with a single space. | |
| 326 spaces(1); | |
| 327 } | |
| 328 | |
| 329 /// Spaces. | |
| 330 void spaces(int num) { | |
| 331 var charsToReplace = countWhitespace(); | |
| 332 addEdit(sourceIndex, charsToReplace, SPACES[num]); | |
| 333 advanceChars(charsToReplace); | |
| 334 } | |
| 335 | |
| 336 wrongToken(String token) { | |
| 337 throw new FormatterException('expected token: "${token}", ' | |
| 338 'actual: "${currentToken}"'); | |
| 339 } | |
| 340 | |
| 341 String toString() => | |
| 342 new EditOperation().apply(editStore.edits, | |
| 343 source.substring(0, sourceIndex)); | |
| 344 | |
| 195 } | 345 } |
| 196 | 346 |
| 197 const SPACE = ' '; | 347 const SPACE = ' '; |
| 348 final SPACES = [ | |
| 349 '', | |
| 350 ' ', | |
| 351 ' ', | |
| 352 ' ', | |
| 353 ' ', | |
| 354 ' ', | |
| 355 ' ', | |
| 356 ' ', | |
| 357 ' ', | |
| 358 ' ', | |
| 359 ' ', | |
| 360 ' ', | |
| 361 ' ', | |
| 362 ' ', | |
| 363 ' ', | |
| 364 ' ', | |
| 365 ' ', | |
| 366 ]; | |
| 367 | |
| 198 | 368 |
| 199 bool isIndentChar(String ch) => ch == SPACE; // TODO(pquitslund) also check tab | 369 bool isIndentChar(String ch) => ch == SPACE; // TODO(pquitslund) also check tab |
| 200 | 370 |
| 201 | 371 |
| 202 /// Manages stored [Edit]s. | 372 /// Manages stored [Edit]s. |
| 203 class EditStore { | 373 class EditStore { |
| 204 | 374 |
| 375 const EditStore(); | |
|
Brian Wilkerson
2013/06/19 21:50:52
I wouldn't make this a 'const' constructor because
pquitslund
2013/06/20 22:34:52
Actually, I think I AM! Honestly, this is an arti
| |
| 376 | |
| 205 /// The underlying sequence of [Edit]s. | 377 /// The underlying sequence of [Edit]s. |
| 206 final edits = <Edit>[]; | 378 final edits = <Edit>[]; |
| 207 | 379 |
| 208 /// Add the given [Edit] to the end of the edit sequence. | 380 /// Add the given [Edit] to the end of the edit sequence. |
| 209 void add(Edit edit) { | 381 void add(Edit edit) { |
| 210 edits.add(edit); | 382 edits.add(edit); |
| 211 } | 383 } |
| 212 | 384 |
| 213 /// Add an [Edit] that describes a textual [replacement] of a text interval | 385 /// Add an [Edit] that describes a textual [replacement] of a text interval |
| 214 /// starting at the given [offset] spanning the given [length]. | 386 /// starting at the given [offset] spanning the given [length]. |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 232 /// Reset cached state. | 404 /// Reset cached state. |
| 233 void reset() { | 405 void reset() { |
| 234 edits.clear(); | 406 edits.clear(); |
| 235 } | 407 } |
| 236 | 408 |
| 237 String toString() => 'EditStore( ${edits.toString()} )'; | 409 String toString() => 'EditStore( ${edits.toString()} )'; |
| 238 | 410 |
| 239 } | 411 } |
| 240 | 412 |
| 241 | 413 |
| 242 | |
| 243 /// Describes a text edit. | 414 /// Describes a text edit. |
| 244 class Edit { | 415 class Edit { |
| 245 | 416 |
| 246 /// The offset at which to apply the edit. | 417 /// The offset at which to apply the edit. |
| 247 final int offset; | 418 final int offset; |
| 248 | 419 |
| 249 /// The length of the text interval to replace. | 420 /// The length of the text interval to replace. |
| 250 final int length; | 421 final int length; |
| 251 | 422 |
| 252 /// The replacement text. | 423 /// The replacement text. |
| 253 final String replacement; | 424 final String replacement; |
| 254 | 425 |
| 255 /// Create an edit. | 426 /// Create an edit. |
| 256 const Edit(this.offset, this.length, this.replacement); | 427 const Edit(this.offset, this.length, this.replacement); |
| 257 | 428 |
| 258 /// Create an edit for the given [range]. | 429 /// Create an edit for the given [range]. |
| 259 Edit.forRange(SourceRange range, String replacement): | 430 Edit.forRange(SourceRange range, String replacement): |
| 260 this(range.offset, range.length, replacement); | 431 this(range.offset, range.length, replacement); |
| 261 | 432 |
| 262 String toString() => '${offset < 0 ? '(' : 'X('} offset: ${offset} , ' | 433 String toString() => '${offset < 0 ? '(' : 'X('} offset: ${offset} , ' |
| 263 'length ${length}, replacement :> ${replacement} <:)'; | 434 'length ${length}, replacement :> ${replacement} <:)'; |
| 264 | 435 |
| 265 } | 436 } |
| 266 | 437 |
| 438 /// Applies a sequence of [edits] to a [document]. | |
| 439 class EditOperation { | |
| 440 | |
| 441 String apply(List<Edit> edits, String document) { | |
| 442 | |
| 443 var edit; | |
| 444 for (var i = edits.length - 1; i >= 0; --i) { | |
| 445 edit = edits[i]; | |
| 446 document = replace(document, edit.offset, | |
| 447 edit.offset + edit.length, edit.replacement); | |
| 448 } | |
| 449 | |
| 450 return document; | |
| 451 } | |
| 452 | |
| 453 } | |
| 454 | |
| 455 | |
| 456 String replace(String str, int start, int end, String replacement) => | |
| 457 str.substring(0, start) + replacement + str.substring(end); | |
| 458 | |
| 459 | |
| 267 /// An AST visitor that drives formatting heuristics. | 460 /// An AST visitor that drives formatting heuristics. |
| 268 class FormattingEngine extends RecursiveASTVisitor<Void> { | 461 class FormattingEngine extends RecursiveASTVisitor { |
| 269 | 462 |
| 270 final FormatterOptions options; | 463 final FormatterOptions options; |
| 271 | 464 |
| 465 CodeKind kind; | |
| 466 EditRecorder recorder; | |
| 467 | |
| 272 FormattingEngine(this.options); | 468 FormattingEngine(this.options); |
| 273 | 469 |
| 470 String format(String source, ASTNode node, Token start, CodeKind kind, | |
| 471 EditRecorder recorder) { | |
| 472 | |
| 473 this.kind = kind; | |
| 474 this.recorder = recorder; | |
| 475 | |
| 476 recorder..source = source | |
| 477 ..currentToken = start; | |
|
Brian Wilkerson
2013/06/19 21:50:52
This looks weird. I would have expected the state
pquitslund
2013/06/20 22:34:52
Agreed. Still working on how this entry point sho
| |
| 478 | |
| 479 node.accept(this); | |
| 480 | |
| 481 var editor = new EditOperation(); | |
| 482 return editor.apply(recorder.editStore.edits, source); | |
| 483 } | |
| 484 | |
| 485 | |
| 486 visitClassDeclaration(ClassDeclaration node) { | |
| 487 | |
| 488 recorder.advanceIndent(); | |
| 489 | |
| 490 if (node.documentationComment != null) { | |
| 491 node.documentationComment.accept(this); | |
| 492 } | |
| 493 | |
| 494 recorder..advance(node.classKeyword)..space(); | |
| 495 | |
| 496 node.name.accept(this); | |
| 497 | |
| 498 if (node.typeParameters != null) { | |
| 499 node.typeParameters.accept(this); | |
| 500 } | |
| 501 recorder.space(); | |
| 502 | |
| 503 if (node.extendsClause != null) { | |
| 504 node.extendsClause.accept(this); | |
| 505 recorder.space(); | |
| 506 } | |
| 507 | |
|
Brian Wilkerson
2013/06/19 21:50:52
You missed the withClause.
pquitslund
2013/06/20 22:34:52
Coming real soon. (Driven by accompanying tests.)
| |
| 508 if (node.implementsClause != null) { | |
| 509 node.implementsClause.accept(this); | |
| 510 recorder.space(); | |
| 511 } | |
| 512 | |
| 513 recorder..advance(node.leftBracket) | |
| 514 ..indent(); | |
| 515 | |
| 516 for (var member in node.members) { | |
| 517 recorder.newline(); | |
|
Brian Wilkerson
2013/06/19 21:50:52
Do you want to advanceIndent() after the newline,
pquitslund
2013/06/20 22:34:52
Actually, yes. That is cleaner. Thanks!
| |
| 518 member.accept(this); | |
| 519 } | |
| 520 | |
| 521 recorder..unindent() | |
| 522 ..newline() | |
| 523 ..advanceIndent() | |
| 524 ..advance(node.rightBracket); | |
| 525 } | |
| 526 | |
| 527 | |
| 528 visitBlockFunctionBody(BlockFunctionBody node) { | |
| 529 recorder..advance(node.beginToken) | |
|
Brian Wilkerson
2013/06/19 21:50:52
The begin token for a BlockFunctionBody is the '{'
| |
| 530 ..indent() | |
| 531 ..newline(); | |
| 532 node.block.accept(this); | |
| 533 recorder..unindent() | |
| 534 ..advanceIndent() | |
| 535 ..advance(node.endToken); | |
| 536 } | |
| 537 | |
| 538 | |
| 539 visitBlock(Block block) { | |
| 540 | |
| 541 } | |
| 542 | |
| 543 | |
| 544 visitExpressionFunctionBody(ExpressionFunctionBody node) { | |
| 545 recorder..advance(node.beginToken) | |
|
Brian Wilkerson
2013/06/19 21:50:52
Here too: use 'functionDefinition' and 'semicolon'
pquitslund
2013/06/20 22:34:52
Thanks!
| |
| 546 ..indent() | |
| 547 ..newline(); | |
| 548 node.expression.accept(this); | |
| 549 recorder..unindent() | |
| 550 ..advanceIndent() | |
| 551 ..advance(node.endToken); | |
| 552 } | |
| 553 | |
| 554 | |
| 555 visitMethodDeclaration(MethodDeclaration node) { | |
| 556 | |
| 557 recorder.advanceIndent(); | |
|
Brian Wilkerson
2013/06/19 21:50:52
The rule for AST nodes is that whitespace before a
pquitslund
2013/06/20 22:34:52
Done.
| |
| 558 | |
| 559 if (node.modifierKeyword != null) { | |
| 560 recorder.advance(node.modifierKeyword); | |
| 561 recorder.space(); | |
| 562 } | |
| 563 | |
| 564 if (node.returnType != null) { | |
| 565 recorder.advance(node.returnType.beginToken); | |
|
Brian Wilkerson
2013/06/19 21:50:52
I think you want to visit the returnType at this p
pquitslund
2013/06/20 22:34:52
Done.
| |
| 566 recorder.space(); | |
| 567 } | |
| 568 | |
| 569 recorder.advance(node.name.beginToken); | |
| 570 | |
| 571 node.parameters.accept(this); | |
| 572 | |
| 573 recorder.space(); | |
| 574 | |
| 575 node.body.accept(this); | |
| 576 } | |
| 577 | |
| 578 | |
| 579 visitFormalParameterList(FormalParameterList node) { | |
| 580 recorder.advance(node.beginToken); | |
| 581 //... | |
| 582 recorder.advance(node.endToken); | |
| 583 } | |
| 584 | |
| 585 | |
| 586 visitSimpleIdentifier(SimpleIdentifier node) { | |
| 587 recorder.advance(node.token); | |
| 588 } | |
| 589 | |
| 274 } | 590 } |
| OLD | NEW |