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