OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 trydart.poi.scope_information_visitor; | 5 library trydart.poi.scope_information_visitor; |
6 | 6 |
7 import 'package:compiler/src/elements/modelx.dart' as modelx; | 7 import 'package:compiler/src/elements/modelx.dart' as modelx; |
8 | 8 |
9 import 'package:compiler/src/elements/modelx.dart' show | 9 import 'package:compiler/src/elements/modelx.dart' show |
10 CompilationUnitElementX, | 10 CompilationUnitElementX, |
(...skipping 11 matching lines...) Expand all Loading... |
22 CompilationUnitElement, | 22 CompilationUnitElement, |
23 Element, | 23 Element, |
24 ElementCategory, | 24 ElementCategory, |
25 FunctionElement, | 25 FunctionElement, |
26 LibraryElement, | 26 LibraryElement, |
27 ScopeContainerElement; | 27 ScopeContainerElement; |
28 | 28 |
29 import 'package:compiler/src/dart_types.dart' show | 29 import 'package:compiler/src/dart_types.dart' show |
30 DartType; | 30 DartType; |
31 | 31 |
32 /****************** IGNORE THIS ONLY FOR SMALL DIFF **************************** | |
33 | |
34 /// Enabled by the option --enable-dart-mind. Controls if this program should | |
35 /// be querying Dart Mind. | |
36 bool isDartMindEnabled = false; | |
37 | |
38 /// Iterator over lines from standard input (or the argument array). | |
39 Iterator<String> stdin; | |
40 | |
41 /// Enabled by the option --simulate-mutation. When true, this program will | |
42 /// only prompt for one file name, and subsequent runs will read | |
43 /// FILENAME.N.dart, where N starts at 1, and is increased on each iteration. | |
44 /// For example, if the program is invoked as: | |
45 /// | |
46 /// dart poi.dart --simulate-mutation test.dart 11 22 33 44 | |
47 /// | |
48 /// The program will first read the file 'test.dart' and compute scope | |
49 /// information about position 11, then position 22 in test.dart.1.dart, then | |
50 /// position 33 in test.dart.2.dart, and finally position 44 in | |
51 /// test.dart.3.dart. | |
52 bool isSimulateMutationEnabled = false; | |
53 | |
54 /// Counts the number of times [runPoi] has been invoked. | |
55 int poiCount; | |
56 | |
57 int globalCounter = 0; | |
58 | |
59 /// Enabled by the option --verbose (or -v). Prints more information than you | |
60 /// really need. | |
61 bool isVerbose = false; | |
62 | |
63 /// Enabled by the option --compile. Also compiles the program after analyzing | |
64 /// the POI. | |
65 bool isCompiler = false; | |
66 | |
67 /// Enabled by the option --minify. Passes the same option to the compiler to | |
68 /// generate minified output. | |
69 bool enableMinification = false; | |
70 | |
71 /// When true (the default value) print serialized scope information at the | |
72 /// provided position. | |
73 const bool PRINT_SCOPE_INFO = | |
74 const bool.fromEnvironment('PRINT_SCOPE_INFO', defaultValue: true); | |
75 | |
76 Stopwatch wallClock = new Stopwatch(); | |
77 | |
78 PoiTask poiTask; | |
79 | |
80 Compiler cachedCompiler; | |
81 | |
82 /// Iterator for reading lines from [io.stdin]. | |
83 class StdinIterator implements Iterator<String> { | |
84 String current; | |
85 | |
86 bool moveNext() { | |
87 current = io.stdin.readLineSync(); | |
88 return true; | |
89 } | |
90 } | |
91 | |
92 printFormattedTime(message, int us) { | |
93 String m = '$message${" " * 65}'.substring(0, 60); | |
94 String i = '${" " * 10}${(us/1000).toStringAsFixed(3)}'; | |
95 i = i.substring(i.length - 10); | |
96 print('$m ${i}ms'); | |
97 } | |
98 | |
99 printWallClock(message) { | |
100 if (!isVerbose) return; | |
101 if (wallClock.isRunning) { | |
102 print('$message'); | |
103 printFormattedTime('--->>>', wallClock.elapsedMicroseconds); | |
104 wallClock.reset(); | |
105 } else { | |
106 print(message); | |
107 } | |
108 } | |
109 | |
110 printVerbose(message) { | |
111 if (!isVerbose) return; | |
112 print(message); | |
113 } | |
114 | |
115 main(List<String> arguments) { | |
116 poiCount = 0; | |
117 wallClock.start(); | |
118 List<String> nonOptionArguments = []; | |
119 for (String argument in arguments) { | |
120 if (argument.startsWith('-')) { | |
121 switch (argument) { | |
122 case '--simulate-mutation': | |
123 isSimulateMutationEnabled = true; | |
124 break; | |
125 case '--enable-dart-mind': | |
126 isDartMindEnabled = true; | |
127 break; | |
128 case '-v': | |
129 case '--verbose': | |
130 isVerbose = true; | |
131 break; | |
132 case '--compile': | |
133 isCompiler = true; | |
134 break; | |
135 case '--minify': | |
136 enableMinification = true; | |
137 break; | |
138 default: | |
139 throw 'Unknown option: $argument.'; | |
140 } | |
141 } else { | |
142 nonOptionArguments.add(argument); | |
143 } | |
144 } | |
145 if (nonOptionArguments.isEmpty) { | |
146 stdin = new StdinIterator(); | |
147 } else { | |
148 stdin = nonOptionArguments.iterator; | |
149 } | |
150 | |
151 FormattingDiagnosticHandler handler = new FormattingDiagnosticHandler(); | |
152 handler | |
153 ..verbose = false | |
154 ..enableColors = true; | |
155 api.CompilerInputProvider inputProvider = handler.provider; | |
156 | |
157 return prompt('Dart file: ').then((String fileName) { | |
158 if (isSimulateMutationEnabled) { | |
159 inputProvider = simulateMutation(fileName, inputProvider); | |
160 } | |
161 return prompt('Position: ').then((String position) { | |
162 return parseUserInput(fileName, position, inputProvider, handler); | |
163 }); | |
164 }); | |
165 } | |
166 | |
167 /// Create an input provider that implements the behavior documented at | |
168 /// [simulateMutation]. | |
169 api.CompilerInputProvider simulateMutation( | |
170 String fileName, | |
171 api.CompilerInputProvider inputProvider) { | |
172 Uri script = Uri.base.resolveUri(new Uri.file(fileName)); | |
173 int count = poiCount; | |
174 Future cache; | |
175 String cachedFileName = script.toFilePath(); | |
176 int counter = ++globalCounter; | |
177 return (Uri uri) { | |
178 if (counter != globalCounter) throw 'Using old provider'; | |
179 printVerbose('fake inputProvider#$counter($uri): $poiCount $count'); | |
180 if (uri == script) { | |
181 if (poiCount == count) { | |
182 cachedFileName = uri.toFilePath(); | |
183 if (count != 0) { | |
184 cachedFileName = '$cachedFileName.$count.dart'; | |
185 } | |
186 printVerbose('Not using cached version of $cachedFileName'); | |
187 cache = new io.File(cachedFileName).readAsBytes().then((data) { | |
188 printVerbose( | |
189 'Read file $cachedFileName: ' | |
190 '${UTF8.decode(data.take(100).toList(), allowMalformed: true)}...'
); | |
191 return data; | |
192 }); | |
193 count++; | |
194 } else { | |
195 printVerbose('Using cached version of $cachedFileName'); | |
196 } | |
197 return cache; | |
198 } else { | |
199 printVerbose('Using original provider for $uri'); | |
200 return inputProvider(uri); | |
201 } | |
202 }; | |
203 } | |
204 | |
205 Future<String> prompt(message) { | |
206 if (stdin is StdinIterator) { | |
207 io.stdout.write(message); | |
208 } | |
209 return io.stdout.flush().then((_) { | |
210 stdin.moveNext(); | |
211 return stdin.current; | |
212 }); | |
213 } | |
214 | |
215 Future queryDartMind(String prefix, String info) { | |
216 // TODO(lukechurch): Use [info] for something. | |
217 String encodedArg0 = Uri.encodeComponent('"$prefix"'); | |
218 String mindQuery = | |
219 'http://dart-mind.appspot.com/rpc' | |
220 '?action=GetExportingPubCompletions' | |
221 '&arg0=$encodedArg0'; | |
222 Uri uri = Uri.parse(mindQuery); | |
223 | |
224 io.HttpClient client = new io.HttpClient(); | |
225 return client.getUrl(uri).then((io.HttpClientRequest request) { | |
226 return request.close(); | |
227 }).then((io.HttpClientResponse response) { | |
228 Completer<String> completer = new Completer<String>(); | |
229 response.transform(UTF8.decoder).listen((contents) { | |
230 completer.complete(contents); | |
231 }); | |
232 return completer.future; | |
233 }); | |
234 } | |
235 | |
236 Future parseUserInput( | |
237 String fileName, | |
238 String positionString, | |
239 api.CompilerInputProvider inputProvider, | |
240 api.DiagnosticHandler handler) { | |
241 Future repeat() { | |
242 printFormattedTime('--->>>', wallClock.elapsedMicroseconds); | |
243 wallClock.reset(); | |
244 | |
245 return prompt('Position: ').then((String positionString) { | |
246 wallClock.reset(); | |
247 return parseUserInput(fileName, positionString, inputProvider, handler); | |
248 }); | |
249 } | |
250 | |
251 printWallClock("\n\n\nparseUserInput('$fileName', '$positionString')"); | |
252 | |
253 Uri script = Uri.base.resolveUri(new Uri.file(fileName)); | |
254 if (positionString == null) return null; | |
255 int position = int.parse( | |
256 positionString, onError: (_) { print('Please enter an integer.'); }); | |
257 if (position == null) return repeat(); | |
258 | |
259 inputProvider(script); | |
260 if (isVerbose) { | |
261 handler( | |
262 script, position, position + 1, | |
263 'Point of interest. ' | |
264 'Cursor is immediately before highlighted character.', | |
265 api.Diagnostic.HINT); | |
266 } | |
267 | |
268 Stopwatch sw = new Stopwatch()..start(); | |
269 | |
270 Future future = runPoi(script, position, inputProvider, handler); | |
271 return future.then((Element element) { | |
272 if (isVerbose) { | |
273 printFormattedTime('Resolving took', sw.elapsedMicroseconds); | |
274 } | |
275 sw.reset(); | |
276 String info = scopeInformation(element, position); | |
277 sw.stop(); | |
278 if (PRINT_SCOPE_INFO) { | |
279 print(info); | |
280 } | |
281 printVerbose('Scope information took ${sw.elapsedMicroseconds}us.'); | |
282 sw..reset()..start(); | |
283 Token token = findToken(element, position); | |
284 String prefix; | |
285 if (token != null) { | |
286 if (token.charOffset + token.charCount <= position) { | |
287 // After the token; in whitespace, or in the beginning of another token. | |
288 prefix = ""; | |
289 } else if (token.kind == IDENTIFIER_TOKEN || | |
290 token.kind == KEYWORD_TOKEN) { | |
291 prefix = token.value.substring(0, position - token.charOffset); | |
292 } | |
293 } | |
294 sw.stop(); | |
295 printVerbose('Find token took ${sw.elapsedMicroseconds}us.'); | |
296 if (isDartMindEnabled && prefix != null) { | |
297 sw..reset()..start(); | |
298 return queryDartMind(prefix, info).then((String dartMindSuggestion) { | |
299 sw.stop(); | |
300 print('Dart Mind ($prefix): $dartMindSuggestion.'); | |
301 printVerbose('Dart Mind took ${sw.elapsedMicroseconds}us.'); | |
302 return repeat(); | |
303 }); | |
304 } else { | |
305 if (isDartMindEnabled) { | |
306 print("Didn't talk to Dart Mind, no identifier at POI ($token)."); | |
307 } | |
308 return repeat(); | |
309 } | |
310 }); | |
311 } | |
312 | |
313 /// Find the token corresponding to [position] in [element]. The method only | |
314 /// works for instances of [PartialElement] or [LibraryElement]. Support for | |
315 /// [LibraryElement] is currently limited, and works only for named libraries. | |
316 Token findToken(modelx.ElementX element, int position) { | |
317 Token beginToken; | |
318 DeclarationSite site = element.declarationSite; | |
319 if (site is PartialElement) { | |
320 beginToken = site.beginToken; | |
321 } else if (element.isLibrary) { | |
322 // TODO(ahe): Generalize support for library elements (and update above | |
323 // documentation). | |
324 modelx.LibraryElementX lib = element; | |
325 var tag = lib.libraryTag; | |
326 if (tag != null) { | |
327 beginToken = tag.libraryKeyword; | |
328 } | |
329 } else { | |
330 beginToken = element.position; | |
331 } | |
332 if (beginToken == null) return null; | |
333 for (Token token = beginToken; token.kind != EOF_TOKEN; token = token.next) { | |
334 if (token.charOffset < position && position <= token.next.charOffset) { | |
335 return token; | |
336 } | |
337 } | |
338 return null; | |
339 } | |
340 | |
341 Future<Element> runPoi( | |
342 Uri script, | |
343 int position, | |
344 api.CompilerInputProvider inputProvider, | |
345 api.DiagnosticHandler handler) { | |
346 Stopwatch sw = new Stopwatch()..start(); | |
347 Uri libraryRoot = Uri.base.resolve('sdk/'); | |
348 Uri packageRoot = Uri.base.resolveUri( | |
349 new Uri.file('${io.Platform.packageRoot}/')); | |
350 | |
351 var options = [ | |
352 '--analyze-main', | |
353 '--no-source-maps', | |
354 '--verbose', | |
355 '--categories=Client,Server', | |
356 '--incremental-support', | |
357 '--disable-type-inference', | |
358 ]; | |
359 | |
360 if (!isCompiler) { | |
361 options.add('--analyze-only'); | |
362 } | |
363 | |
364 if (enableMinification) { | |
365 options.add('--minify'); | |
366 } | |
367 | |
368 LibraryUpdater updater; | |
369 | |
370 Future<bool> reuseLibrary(LibraryElement library) { | |
371 return poiTask.measure(() => updater.reuseLibrary(library)); | |
372 } | |
373 | |
374 Future<Compiler> invokeReuseCompiler() { | |
375 updater = new LibraryUpdater( | |
376 cachedCompiler, inputProvider, script, printWallClock, printVerbose); | |
377 return reuseCompiler( | |
378 diagnosticHandler: handler, | |
379 inputProvider: inputProvider, | |
380 options: options, | |
381 cachedCompiler: cachedCompiler, | |
382 libraryRoot: libraryRoot, | |
383 packageRoot: packageRoot, | |
384 packagesAreImmutable: true, | |
385 reuseLibrary: reuseLibrary); | |
386 } | |
387 | |
388 return invokeReuseCompiler().then((Compiler newCompiler) { | |
389 // TODO(ahe): Move this "then" block to [reuseCompiler]. | |
390 if (updater.failed) { | |
391 cachedCompiler = null; | |
392 return invokeReuseCompiler(); | |
393 } else { | |
394 return newCompiler; | |
395 } | |
396 }).then((Compiler newCompiler) { | |
397 if (!isCompiler) { | |
398 newCompiler.enqueuerFilter = new ScriptOnlyFilter(script); | |
399 } | |
400 return runPoiInternal(newCompiler, sw, updater, position); | |
401 }); | |
402 } | |
403 | |
404 Future<Element> runPoiInternal( | |
405 Compiler newCompiler, | |
406 Stopwatch sw, | |
407 LibraryUpdater updater, | |
408 int position) { | |
409 bool isFullCompile = cachedCompiler != newCompiler; | |
410 cachedCompiler = newCompiler; | |
411 if (poiTask == null || poiTask.compiler != cachedCompiler) { | |
412 poiTask = new PoiTask(cachedCompiler); | |
413 cachedCompiler.tasks.add(poiTask); | |
414 } | |
415 | |
416 if (!isFullCompile) { | |
417 printFormattedTime( | |
418 'Analyzing changes and updating elements took', sw.elapsedMicroseconds); | |
419 } | |
420 sw.reset(); | |
421 | |
422 Future<bool> compilation; | |
423 | |
424 if (updater.hasPendingUpdates) { | |
425 compilation = new Future(() { | |
426 var node = js.statement( | |
427 r'var $dart_patch = #', js.escapedString(updater.computeUpdateJs())); | |
428 print(updater.prettyPrintJs(node)); | |
429 | |
430 return !cachedCompiler.compilationFailed; | |
431 }); | |
432 } else { | |
433 compilation = cachedCompiler.run(updater.uri); | |
434 } | |
435 | |
436 return compilation.then((success) { | |
437 printVerbose('Compiler queue processed in ${sw.elapsedMicroseconds}us'); | |
438 if (isVerbose) { | |
439 for (final task in cachedCompiler.tasks) { | |
440 int time = task.timingMicroseconds; | |
441 if (time != 0) { | |
442 printFormattedTime('${task.name} took', time); | |
443 } | |
444 } | |
445 } | |
446 | |
447 if (poiCount != null) poiCount++; | |
448 if (success != true) { | |
449 throw 'Compilation failed'; | |
450 } | |
451 return findPosition(position, cachedCompiler.mainApp); | |
452 }); | |
453 } | |
454 | |
455 Element findPosition(int position, Element element) { | |
456 FindPositionVisitor visitor = new FindPositionVisitor(position, element); | |
457 element.accept(visitor); | |
458 return visitor.element; | |
459 } | |
460 | |
461 String scopeInformation(Element element, int position) { | |
462 ScopeInformationVisitor visitor = | |
463 new ScopeInformationVisitor(element, position); | |
464 element.accept(visitor); | |
465 return '${visitor.buffer}'; | |
466 } | |
467 | |
468 class FindPositionVisitor extends ElementVisitor { | |
469 final int position; | |
470 Element element; | |
471 | |
472 FindPositionVisitor(this.position, this.element); | |
473 | |
474 visitElement(modelx.ElementX e) { | |
475 DeclarationSite site = e.declarationSite; | |
476 if (site is PartialElement) { | |
477 if (site.beginToken.charOffset <= position && | |
478 position < site.endToken.next.charOffset) { | |
479 element = e; | |
480 } | |
481 } | |
482 } | |
483 | |
484 visitClassElement(ClassElement e) { | |
485 if (e is PartialClassElement) { | |
486 if (e.beginToken.charOffset <= position && | |
487 position < e.endToken.next.charOffset) { | |
488 element = e; | |
489 visitScopeContainerElement(e); | |
490 } | |
491 } | |
492 } | |
493 | |
494 visitScopeContainerElement(ScopeContainerElement e) { | |
495 e.forEachLocalMember((Element element) => element.accept(this)); | |
496 } | |
497 } | |
498 | |
499 class ScriptOnlyFilter implements QueueFilter { | |
500 final Uri script; | |
501 | |
502 ScriptOnlyFilter(this.script); | |
503 | |
504 bool checkNoEnqueuedInvokedInstanceMethods(Enqueuer enqueuer) => true; | |
505 | |
506 void processWorkItem(void f(WorkItem work), WorkItem work) { | |
507 if (work.element.library.canonicalUri == script) { | |
508 f(work); | |
509 printWallClock('Processed ${work.element}.'); | |
510 } else { | |
511 printWallClock('Skipped ${work.element}.'); | |
512 } | |
513 } | |
514 } | |
515 | |
516 *******************************************************************************/ | |
517 | |
518 /** | 32 /** |
519 * Serializes scope information about an element. This is accomplished by | 33 * Serializes scope information about an element. This is accomplished by |
520 * calling the [serialize] method on each element. Some elements need special | 34 * calling the [serialize] method on each element. Some elements need special |
521 * treatment, as their enclosing scope must also be serialized. | 35 * treatment, as their enclosing scope must also be serialized. |
522 */ | 36 */ |
523 class ScopeInformationVisitor extends ElementVisitor/* <void> */ { | 37 class ScopeInformationVisitor extends ElementVisitor/* <void> */ { |
524 // TODO(ahe): Include function parameters and local variables. | 38 // TODO(ahe): Include function parameters and local variables. |
525 | 39 |
526 final Compiler compiler; | 40 final Compiler compiler; |
527 final Element currentElement; | 41 final Element currentElement; |
(...skipping 256 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
784 } | 298 } |
785 return result; | 299 return result; |
786 } | 300 } |
787 } | 301 } |
788 | 302 |
789 modelx.ScopeX localScope(modelx.LibraryElementX element) => element.localScope; | 303 modelx.ScopeX localScope(modelx.LibraryElementX element) => element.localScope; |
790 | 304 |
791 modelx.ImportScope importScope(modelx.LibraryElementX element) { | 305 modelx.ImportScope importScope(modelx.LibraryElementX element) { |
792 return element.importScope; | 306 return element.importScope; |
793 } | 307 } |
OLD | NEW |