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 |