Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library formatter_impl; | |
| 6 | |
| 7 | |
| 8 import 'dart:io'; | |
|
Bob Nystrom
2013/06/06 22:23:06
Nit, but Nathan and I put organize our imports lik
pquitslund
2013/06/07 19:45:01
Done.
| |
| 9 import 'package:analyzer_experimental/src/generated/scanner.dart'; | |
| 10 import 'package:analyzer_experimental/analyzer.dart'; | |
| 11 import 'package:analyzer_experimental/src/generated/parser.dart'; | |
| 12 import 'package:analyzer_experimental/src/generated/source.dart'; | |
|
Bob Nystrom
2013/06/06 22:23:06
These are fine, but you can also use relative impo
pquitslund
2013/06/07 19:45:01
I thought this would lead to woes if I ever wanted
Bob Nystrom
2013/06/10 21:15:46
It's very confusing, unfortunately. The basic rule
| |
| 13 | |
| 14 | |
| 15 /// OS line separator. (TODO: may not be necessary) | |
|
messick
2013/06/07 13:51:36
TODO(ldap) everywhere? It probably seems redundant
pquitslund
2013/06/07 19:45:01
Done.
| |
| 16 const String NEW_LINE = '\n' ; //Platform.pathSeparator; | |
|
Bob Nystrom
2013/06/06 22:23:06
Ditch the type annotation.
pquitslund
2013/06/07 19:45:01
Oh my. This is taking some getting used to. :) D
| |
| 17 | |
| 18 /// Formatter options. | |
| 19 class FormatterOptions { | |
| 20 | |
| 21 /// Create formatter options with defaults derived (where defined) from | |
| 22 /// the style guide: <http://www.dartlang.org/articles/style-guide/>. | |
| 23 const FormatterOptions({this.initialIndentationLevel:0, | |
|
Bob Nystrom
2013/06/06 22:23:06
Space after ":".
pquitslund
2013/06/07 19:45:01
Done. If only I had a formatter to do this for me
| |
| 24 this.indentPerLevel:2, | |
| 25 this.lineSeparator:NEW_LINE, | |
| 26 this.pageWidth:80, | |
| 27 this.tabSize:2}); | |
| 28 | |
| 29 final String lineSeparator; | |
| 30 final int initialIndentationLevel; | |
| 31 final int indentPerLevel; | |
| 32 final int tabSize; | |
| 33 final int pageWidth; | |
| 34 } | |
| 35 | |
| 36 | |
| 37 /// Thrown when an error occurs in formatting. | |
| 38 class FormatterException implements Exception { | |
| 39 | |
| 40 /// A message describing the error. | |
| 41 final String message; | |
| 42 | |
| 43 /// Creates a new FormatterException with an optional error [message]. | |
| 44 const FormatterException([this.message = '']); | |
| 45 | |
| 46 FormatterException.forError(List<AnalysisError> errors) : | |
| 47 // TODO: add descriptive message based on errors | |
| 48 message = 'an analysis error occured during format'; | |
| 49 | |
| 50 String toString() => 'FormatterException: $message'; | |
| 51 | |
| 52 } | |
| 53 | |
| 54 /// Specifies the kind of code snippet to format. | |
| 55 class CodeKind { | |
| 56 | |
| 57 final int index; | |
| 58 | |
| 59 const CodeKind(this.index); | |
| 60 | |
| 61 /// A compilation unit snippet. | |
| 62 static const COMPILATION_UNIT = const CodeKind(0); | |
| 63 | |
| 64 /// A statement snippet. | |
| 65 static const STATEMENT = const CodeKind(1); | |
| 66 | |
| 67 } | |
| 68 | |
| 69 /// Dart source code formatter. | |
| 70 abstract class CodeFormatter { | |
|
messick
2013/06/07 13:51:36
I'm not clear what your plan is but I wasn't expec
pquitslund
2013/06/07 19:45:01
See response to Bob below.
| |
| 71 | |
| 72 factory CodeFormatter([FormatterOptions options = const FormatterOptions()]) | |
| 73 => new CodeFormatterImpl(options); | |
| 74 | |
| 75 /// Format the specified portion (from [offset] with [length]) of the given | |
| 76 /// [source] string, optionally providing an [indentationLevel]. | |
| 77 String format(CodeKind kind, String source, {int offset, int end, | |
| 78 int indentationLevel:0}); | |
|
Bob Nystrom
2013/06/06 22:23:06
Indent a line continuation +4, not +2.
pquitslund
2013/06/07 19:45:01
Done.
| |
| 79 | |
| 80 } | |
| 81 | |
| 82 class CodeFormatterImpl implements CodeFormatter, AnalysisErrorListener { | |
|
Bob Nystrom
2013/06/06 22:23:06
Unify this with CodeFormatter. Simpler = better. :
pquitslund
2013/06/07 19:45:01
OK. I *might*. My thinking here is that I really
Brian Wilkerson
2013/06/07 19:55:11
Another alternative would be to not implement Anal
| |
| 83 | |
| 84 final FormatterOptions options; | |
| 85 final List<AnalysisError> errors; | |
| 86 | |
| 87 CodeFormatterImpl(this.options) : errors = new List<AnalysisError>(); | |
| 88 | |
| 89 String format(CodeKind kind, String source, {int offset, int end, | |
| 90 int indentationLevel:0}) { | |
| 91 | |
| 92 var start = tokenize(source); | |
| 93 _checkForErrors(); | |
| 94 | |
| 95 var node = parse(kind, start); | |
| 96 _checkForErrors(); | |
| 97 | |
| 98 // To be continued... | |
| 99 | |
| 100 return source; | |
| 101 } | |
| 102 | |
| 103 ASTNode parse(CodeKind kind, Token start) { | |
|
Bob Nystrom
2013/06/06 22:23:06
The style guide would name this "AstNode".
messick
2013/06/07 13:51:36
We should talk to Konstantin to see if we can get
pquitslund
2013/06/07 19:45:01
Aha. Something for Konstantin (or Brian).
Thanks
pquitslund
2013/06/07 19:45:01
Good point!
| |
| 104 | |
| 105 var parser = new Parser(null, this); | |
| 106 | |
| 107 switch (kind) { | |
| 108 case CodeKind.COMPILATION_UNIT: | |
| 109 return parser.parseCompilationUnit(start); | |
| 110 case CodeKind.STATEMENT: | |
| 111 return parser.parseStatement(start); | |
|
messick
2013/06/07 13:51:36
At a guess, I'd expect refactoring to want a few m
pquitslund
2013/06/07 19:45:01
Absolutely. Need to crawl before I can walk thoug
| |
| 112 } | |
| 113 | |
| 114 throw new FormatterException('Unsupported format kind: $kind'); | |
| 115 } | |
| 116 | |
| 117 _checkForErrors() { | |
| 118 if (errors.length > 0) { | |
| 119 throw new FormatterException.forError(errors); | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 void onError(AnalysisError error){ | |
|
Bob Nystrom
2013/06/06 22:23:06
Space before "{".
pquitslund
2013/06/07 19:45:01
Eagle eye. Fixed!
| |
| 124 errors.add(error); | |
| 125 } | |
| 126 | |
| 127 Token tokenize(String source) { | |
| 128 var scanner = new StringScanner(null, source, this); | |
| 129 return scanner.tokenize(); | |
|
Brian Wilkerson
2013/06/06 22:21:58
I noticed that you're testing for new-lines in the
pquitslund
2013/06/07 19:45:01
Cool. Great tip. I'll look into that. Thanks!
| |
| 130 } | |
| 131 | |
| 132 } | |
| 133 | |
| 134 /// Placeholder class to hold a reference to the Class object representing | |
| 135 /// the Dart keyword void. | |
| 136 class Void extends Object { | |
|
messick
2013/06/07 13:51:36
I hope you'll be able to use Keyword.VOID instead
pquitslund
2013/06/07 19:45:01
The issue is that I really like to use VOID as a t
| |
| 137 | |
| 138 } | |
| 139 | |
| 140 | |
| 141 /// Records a sequence of edits to a source string that will cause the string | |
| 142 /// to be formatted when applied. | |
| 143 class EditRecorder { | |
| 144 | |
| 145 final FormatterOptions options; | |
| 146 | |
| 147 int column = 0; | |
| 148 | |
| 149 int sourceIndex = 0; | |
| 150 String source = ''; | |
| 151 | |
| 152 Token currentToken; | |
| 153 | |
| 154 int indentationLevel = 0; | |
| 155 int numberOfIndentations = 0; | |
| 156 | |
| 157 bool isIndentNeeded = false; | |
| 158 | |
| 159 EditRecorder(this.options); | |
| 160 | |
| 161 /// Count the number of whitespace chars beginning at the current | |
| 162 /// [sourceIndex]. | |
| 163 int countWhitespace() { | |
| 164 var count = 0; | |
| 165 for (var i = sourceIndex; i < source.length; ++i) { | |
| 166 if (isIndentChar(source[i])) { | |
| 167 ++count; | |
| 168 } else { | |
| 169 break; | |
| 170 } | |
| 171 } | |
| 172 return count; | |
| 173 } | |
| 174 | |
| 175 /// Indent. | |
| 176 void indent() { | |
| 177 indentationLevel += options.indentPerLevel; | |
|
messick
2013/06/07 13:51:36
These names are confusing. What's a "level"?
inden
pquitslund
2013/06/07 19:45:01
Working on it. Thanks!
| |
| 178 numberOfIndentations++; | |
| 179 } | |
| 180 | |
| 181 /// Test if there is a newline at the given source [index]. | |
| 182 bool isNewlineAt(int index) { | |
| 183 if (index < 0 || index + NEW_LINE.length > source.length) { | |
| 184 return false; | |
| 185 } | |
| 186 for (var i = 0; i < NEW_LINE.length; i++) { | |
|
Bob Nystrom
2013/06/06 22:23:06
Do you need to increment index here too?
pquitslund
2013/06/07 19:45:01
Time for some more tests! :)
| |
| 187 if (source[index] != NEW_LINE[i]) { | |
| 188 return false; | |
| 189 } | |
| 190 } | |
| 191 return true; | |
| 192 } | |
| 193 | |
| 194 } | |
| 195 | |
| 196 final String SPACE = ' '; | |
|
Bob Nystrom
2013/06/06 22:23:06
"final String" -> "const".
pquitslund
2013/06/07 19:45:01
Done.
| |
| 197 | |
| 198 bool isIndentChar(String ch) => ch == SPACE; // TODO(pquitslund) also check tab | |
| 199 | |
| 200 | |
| 201 /// Manages stored [Edit]s. | |
| 202 abstract class EditStore { | |
|
Brian Wilkerson
2013/06/06 22:21:58
Edits and edit stores sound like a re-usable piece
messick
2013/06/07 13:51:36
I think so, too.
| |
| 203 | |
| 204 /// Add an [Edit] that describes a textual [replacement] of a text interval | |
| 205 /// starting at the given [offset] spanning the given [length]. | |
| 206 void addEdit(int offset, int length, String replacement); | |
| 207 | |
| 208 /// Get the index of the current edit (for use in caching location | |
| 209 /// information). | |
| 210 int getCurrentEditIndex(); | |
| 211 | |
| 212 /// Get the underlying sequence of [Edit]s. | |
| 213 List<Edit> get edits; | |
| 214 | |
| 215 /// Get the last edit. | |
| 216 Edit getLastEdit(); | |
|
Bob Nystrom
2013/06/06 22:23:06
You'll already have edits.last, so I'd ditch this.
pquitslund
2013/06/07 19:45:01
Right but for some reason I convinced myself that
Bob Nystrom
2013/06/10 21:15:46
They could just do edits.isEmpty to check for that
| |
| 217 | |
| 218 /// Add an [Edit] that describes an insertion of text starting at the given | |
| 219 /// [offset]. | |
| 220 void insert(int offset, String insertedString); | |
| 221 | |
| 222 /// Reset cached state. | |
| 223 void reset(); | |
| 224 | |
| 225 } | |
| 226 | |
| 227 /// Basic un-optimized [EditStore] suitable for subclassing. | |
| 228 class BasicEditStore implements EditStore { | |
|
Bob Nystrom
2013/06/06 22:23:06
You can unify this with EditStore. If users don't
pquitslund
2013/06/07 19:45:01
Ah yes. Good point!
Done.
| |
| 229 | |
| 230 final List<Edit> edits = new List<Edit>(); | |
|
Bob Nystrom
2013/06/06 22:23:06
final edits = <Edit>[];
pquitslund
2013/06/07 19:45:01
Done.
| |
| 231 | |
| 232 /// Add the given [Edit] to the end of the edit sequence. | |
| 233 void add(Edit edit) { | |
| 234 edits.add(edit); | |
| 235 } | |
| 236 | |
| 237 void addEdit(int offset, int length, String replacement) { | |
| 238 add(new Edit(offset, length, replacement)); | |
| 239 } | |
| 240 | |
| 241 //TODO(pquitslund): verify that this should be "last" vs. "next" | |
|
messick
2013/06/07 13:51:36
I wonder if this comment is in the right place?
pquitslund
2013/06/07 19:45:01
Done.
| |
| 242 int getCurrentEditIndex() => edits.length - 1; | |
| 243 | |
| 244 Edit getLastEdit() => edits.isEmpty ? null : edits.last; | |
| 245 | |
| 246 void insert(int offset, String insertedString) { | |
| 247 addEdit(offset, 0, insertedString); | |
| 248 } | |
| 249 | |
| 250 void reset() { | |
| 251 edits.clear(); | |
| 252 } | |
| 253 | |
| 254 String toString() => 'EditStore( ${edits.toString()} )'; | |
| 255 | |
| 256 } | |
|
Bob Nystrom
2013/06/06 22:23:06
Unindent.
pquitslund
2013/06/07 19:45:01
Nothing escapes you!
| |
| 257 | |
| 258 | |
| 259 | |
| 260 /// Describes a text edit. | |
| 261 class Edit { | |
| 262 | |
| 263 /// The offset at which to apply the edit. | |
| 264 final int offset; | |
| 265 | |
| 266 /// The length of the text interval to replace. | |
| 267 final int length; | |
| 268 | |
| 269 /// The replacement text. | |
| 270 final String replacement; | |
| 271 | |
| 272 /// Create an edit. | |
| 273 const Edit(this.offset, this.length, this.replacement); | |
| 274 | |
| 275 /// Create an edit for the given [range]. | |
| 276 Edit.forRange(SourceRange range, String replacement): | |
| 277 this(range.offset, range.length, replacement); | |
| 278 | |
| 279 String toString() => '${offset < 0 ? '(' : 'X('} offset: ${offset} , ' | |
| 280 'length ${length}, replacement :> ${replacement} <:)'; | |
| 281 | |
| 282 } | |
| 283 | |
| 284 /// An AST visitor that drives formatting heuristics. | |
| 285 class FormattingEngine extends RecursiveASTVisitor<Void> { | |
| 286 | |
| 287 final FormatterOptions options; | |
| 288 | |
| 289 FormattingEngine(this.options); | |
| 290 | |
| 291 } | |
| OLD | NEW |