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