| 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 |