Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 /// Source information system mapping that attempts a semantic mapping between | 5 /// Source information system mapping that attempts a semantic mapping between |
| 6 /// offsets of JavaScript code points to offsets of Dart code points. | 6 /// offsets of JavaScript code points to offsets of Dart code points. |
| 7 | 7 |
| 8 library dart2js.source_information.position; | 8 library dart2js.source_information.position; |
| 9 | 9 |
| 10 import '../common.dart'; | 10 import '../common.dart'; |
| 11 import '../elements/elements.dart' show | 11 import '../elements/elements.dart' show |
| 12 AstElement, | 12 AstElement, |
| 13 LocalElement; | 13 LocalElement; |
| 14 import '../js/js.dart' as js; | 14 import '../js/js.dart' as js; |
| 15 import '../js/js_source_mapping.dart'; | 15 import '../js/js_source_mapping.dart'; |
| 16 import '../js/js_debug.dart'; | 16 import '../js/js_debug.dart'; |
| 17 import '../tree/tree.dart' show | 17 import '../tree/tree.dart' show |
| 18 Node, | 18 Node, |
| 19 Send; | 19 Send; |
| 20 | 20 |
| 21 import 'code_output.dart' show | |
| 22 CodeBuffer; | |
| 21 import 'source_file.dart'; | 23 import 'source_file.dart'; |
| 22 import 'source_information.dart'; | 24 import 'source_information.dart'; |
| 23 | 25 |
| 24 /// [SourceInformation] that consists of an offset position into the source | 26 /// [SourceInformation] that consists of an offset position into the source |
| 25 /// code. | 27 /// code. |
| 26 class PositionSourceInformation extends SourceInformation { | 28 class PositionSourceInformation extends SourceInformation { |
| 27 @override | 29 @override |
| 28 final SourceLocation startPosition; | 30 final SourceLocation startPosition; |
| 29 | 31 |
| 30 @override | 32 @override |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 106 | 108 |
| 107 @override | 109 @override |
| 108 SourceInformationBuilder createBuilderForContext(AstElement element) { | 110 SourceInformationBuilder createBuilderForContext(AstElement element) { |
| 109 return new PositionSourceInformationBuilder(element); | 111 return new PositionSourceInformationBuilder(element); |
| 110 } | 112 } |
| 111 | 113 |
| 112 @override | 114 @override |
| 113 SourceInformationProcessor createProcessor(SourceMapper mapper) { | 115 SourceInformationProcessor createProcessor(SourceMapper mapper) { |
| 114 return new PositionSourceInformationProcessor(mapper); | 116 return new PositionSourceInformationProcessor(mapper); |
| 115 } | 117 } |
| 118 | |
| 119 @override | |
| 120 void onComplete() {} | |
| 121 | |
| 122 @override | |
| 123 SourceInformation buildSourceMappedMarker() { | |
| 124 return const SourceMappedMarker(); | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 /// Marker used to tag the root nodes of source-mapped code. | |
| 129 /// | |
| 130 /// This is needed to be able to distinguish JavaScript nodes that shouldn't | |
| 131 /// have source locations (like the premable) from the nodes that should | |
| 132 /// (like functions compiled from Dart code). | |
| 133 class SourceMappedMarker extends SourceInformation { | |
| 134 const SourceMappedMarker(); | |
| 135 | |
| 136 @override | |
| 137 String get shortText => ''; | |
| 138 | |
| 139 @override | |
| 140 List<SourceLocation> get sourceLocations => const <SourceLocation>[]; | |
| 141 | |
| 142 @override | |
| 143 SourceSpan get sourceSpan => new SourceSpan(null, null, null); | |
| 116 } | 144 } |
| 117 | 145 |
| 118 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. | 146 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. |
| 119 class PositionSourceInformationBuilder implements SourceInformationBuilder { | 147 class PositionSourceInformationBuilder implements SourceInformationBuilder { |
| 120 final SourceFile sourceFile; | 148 final SourceFile sourceFile; |
| 121 final String name; | 149 final String name; |
| 122 | 150 |
| 123 PositionSourceInformationBuilder(AstElement element) | 151 PositionSourceInformationBuilder(AstElement element) |
| 124 : sourceFile = element.implementation.compilationUnit.script.file, | 152 : sourceFile = element.implementation.compilationUnit.script.file, |
| 125 name = computeElementNameForSourceMaps(element); | 153 name = computeElementNameForSourceMaps(element); |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 218 } | 246 } |
| 219 } | 247 } |
| 220 | 248 |
| 221 /// The start, end and closing offsets for a [js.Node]. | 249 /// The start, end and closing offsets for a [js.Node]. |
| 222 class CodePosition { | 250 class CodePosition { |
| 223 final int startPosition; | 251 final int startPosition; |
| 224 final int endPosition; | 252 final int endPosition; |
| 225 final int closingPosition; | 253 final int closingPosition; |
| 226 | 254 |
| 227 CodePosition(this.startPosition, this.endPosition, this.closingPosition); | 255 CodePosition(this.startPosition, this.endPosition, this.closingPosition); |
| 256 | |
| 257 int getPosition(CodePositionKind kind) { | |
| 258 switch (kind) { | |
| 259 case CodePositionKind.START: | |
| 260 return startPosition; | |
| 261 case CodePositionKind.END: | |
| 262 return endPosition; | |
| 263 case CodePositionKind.CLOSING: | |
| 264 return closingPosition; | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 String toString() { | |
| 269 return 'CodePosition(start=$startPosition,' | |
| 270 'end=$endPosition,closing=$closingPosition)'; | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 /// A map from a [js.Node] to its [CodePosition]. | |
| 275 abstract class CodePositionMap { | |
| 276 CodePosition operator [](js.Node node); | |
| 228 } | 277 } |
| 229 | 278 |
| 230 /// Registry for mapping [js.Node]s to their [CodePosition]. | 279 /// Registry for mapping [js.Node]s to their [CodePosition]. |
| 231 class CodePositionRecorder { | 280 class CodePositionRecorder implements CodePositionMap { |
| 232 Map<js.Node, CodePosition> _codePositionMap = | 281 Map<js.Node, CodePosition> _codePositionMap = |
| 233 new Map<js.Node, CodePosition>.identity(); | 282 new Map<js.Node, CodePosition>.identity(); |
| 234 | 283 |
| 235 void registerPositions(js.Node node, | 284 void registerPositions(js.Node node, |
| 236 int startPosition, | 285 int startPosition, |
| 237 int endPosition, | 286 int endPosition, |
| 238 int closingPosition) { | 287 int closingPosition) { |
| 239 registerCodePosition(node, | 288 registerCodePosition(node, |
| 240 new CodePosition(startPosition, endPosition, closingPosition)); | 289 new CodePosition(startPosition, endPosition, closingPosition)); |
| 241 } | 290 } |
| 242 | 291 |
| 243 void registerCodePosition(js.Node node, CodePosition codePosition) { | 292 void registerCodePosition(js.Node node, CodePosition codePosition) { |
| 244 _codePositionMap[node] = codePosition; | 293 _codePositionMap[node] = codePosition; |
| 245 } | 294 } |
| 246 | 295 |
| 247 CodePosition operator [](js.Node node) => _codePositionMap[node]; | 296 CodePosition operator [](js.Node node) => _codePositionMap[node]; |
| 248 } | 297 } |
| 249 | 298 |
| 299 /// Enum values for the part of a Dart node used for the source location offset. | |
| 250 enum SourcePositionKind { | 300 enum SourcePositionKind { |
| 301 /// The source mapping should point to the start of the Dart node. | |
| 302 /// | |
| 303 /// For instance the first '(' for the `(*)()` call and 'f' of both the | |
| 304 /// `foo()` and the `*.bar()` call: | |
| 305 /// | |
| 306 /// (foo().bar())() | |
| 307 /// ^ // the start of the `(*)()` node | |
| 308 /// ^ // the start of the `foo()` node | |
| 309 /// ^ // the start of the `*.bar()` node | |
| 310 /// | |
| 251 START, | 311 START, |
| 312 | |
| 313 /// The source mapping should point an inner position of the Dart node. | |
| 314 /// | |
| 315 /// For instance the second '(' of the `(*)()` call, the 'f' of the `foo()` | |
| 316 /// call and the 'b' of the `*.bar()` call: | |
| 317 /// | |
| 318 /// (foo().bar())() | |
| 319 /// ^ // the inner position of the `(*)()` node | |
| 320 /// ^ // the inner position of the `foo()` node | |
| 321 /// ^ // the inner position of the `*.bar()` node | |
| 322 /// | |
| 323 /// For function expressions the inner position is the closing brace or the | |
| 324 /// arrow: | |
| 325 /// | |
| 326 /// foo() => () {} | |
| 327 /// ^ // the inner position of the 'foo' function | |
| 328 /// ^ // the inner position of the closure | |
| 329 /// | |
| 330 INNER, | |
| 331 } | |
| 332 | |
| 333 SourceLocation getSourceLocation( | |
| 334 SourceInformation sourceInformation, | |
| 335 [SourcePositionKind sourcePositionKind = SourcePositionKind.START]) { | |
| 336 if (sourceInformation == null) return null; | |
| 337 switch (sourcePositionKind) { | |
| 338 case SourcePositionKind.START: | |
| 339 return sourceInformation.startPosition; | |
| 340 case SourcePositionKind.INNER: | |
| 341 return sourceInformation.closingPosition; | |
| 342 } | |
| 343 } | |
| 344 | |
| 345 /// Enum values for the part of the JavaScript node used for the JavaScript | |
| 346 /// code offset of a source mapping. | |
| 347 enum CodePositionKind { | |
| 348 /// The source mapping is put on left-most offset of the node. | |
| 349 /// | |
| 350 /// For instance on the 'f' of a function or 'r' of a return statement: | |
| 351 /// | |
| 352 /// foo: function() { return 0; } | |
| 353 /// ^ // the function start position | |
| 354 /// ^ // the return start position | |
| 355 START, | |
| 356 | |
| 357 /// The source mapping is put on the closing token. | |
| 358 /// | |
| 359 /// For instance on the '}' of a function or the ';' of a return statement: | |
| 360 /// | |
| 361 /// foo: function() { return 0; } | |
| 362 /// ^ // the function closing position | |
| 363 /// ^ // the return closing position | |
| 364 /// | |
| 252 CLOSING, | 365 CLOSING, |
| 253 END, | 366 |
| 254 } | 367 /// The source mapping is put at the end of the code for the node. |
| 255 | 368 /// |
| 256 enum CodePositionKind { | 369 /// For instance after '}' of a function or after the ';' of a return |
| 257 START, | 370 /// statement: |
| 258 CLOSING, | 371 /// |
| 372 /// foo: function() { return 0; } | |
| 373 /// ^ // the function end position | |
| 374 /// ^ // the return end position | |
| 375 /// | |
| 259 END, | 376 END, |
| 260 } | 377 } |
| 261 | 378 |
| 262 /// Processor that associates [SourceLocation]s from [SourceInformation] on | 379 /// Processor that associates [SourceLocation]s from [SourceInformation] on |
| 263 /// [js.Node]s with the target offsets in a [SourceMapper]. | 380 /// [js.Node]s with the target offsets in a [SourceMapper]. |
| 264 class PositionSourceInformationProcessor | 381 class PositionSourceInformationProcessor implements SourceInformationProcessor { |
| 265 extends js.BaseVisitor implements SourceInformationProcessor { | 382 final CodePositionRecorder codePositionRecorder = new CodePositionRecorder(); |
| 266 final CodePositionRecorder codePositions = new CodePositionRecorder(); | 383 CodePositionMap codePositionMap; |
| 267 final SourceMapper sourceMapper; | 384 List<TraceListener> traceListeners; |
| 268 | 385 |
| 269 PositionSourceInformationProcessor(this.sourceMapper); | 386 PositionSourceInformationProcessor( |
| 270 | 387 SourceMapper sourceMapper, |
| 271 void process(js.Node node) { | 388 [Coverage coverage]) { |
| 272 node.accept(this); | 389 codePositionMap = coverage != null |
| 273 } | 390 ? new CodePositionCoverage(codePositionRecorder, coverage) |
| 274 | 391 : codePositionRecorder; |
| 275 void visitChildren(js.Node node) { | 392 traceListeners = [new PositionTraceListener(sourceMapper)]; |
| 276 node.visitChildren(this); | 393 if (coverage != null) { |
| 277 } | 394 traceListeners.add(new CoverageListener(coverage)); |
| 278 | 395 } |
| 279 CodePosition getCodePosition(js.Node node) { | 396 } |
| 280 return codePositions[node]; | 397 |
| 281 } | 398 void process(js.Node node, CodeBuffer codeBuffer) { |
| 282 | 399 new JavaScriptTracer(codePositionMap, traceListeners).apply(node); |
| 283 /// Associates [sourceInformation] with the JavaScript [node]. | |
| 284 /// | |
| 285 /// The offset into the JavaScript code is computed by pulling the | |
| 286 /// [codePositionKind] from the code positions associated with | |
| 287 /// [codePositionNode]. | |
| 288 /// | |
| 289 /// The mapped Dart source location is computed by pulling the | |
| 290 /// [sourcePositionKind] source location from [sourceInformation]. | |
| 291 void apply(js.Node node, | |
| 292 js.Node codePositionNode, | |
| 293 CodePositionKind codePositionKind, | |
| 294 SourceInformation sourceInformation, | |
| 295 SourcePositionKind sourcePositionKind) { | |
| 296 if (sourceInformation != null) { | |
| 297 CodePosition codePosition = getCodePosition(codePositionNode); | |
| 298 // We should always have recorded the needed code positions. | |
| 299 assert(invariant( | |
| 300 NO_LOCATION_SPANNABLE, | |
| 301 codePosition != null, | |
| 302 message: | |
| 303 "Code position missing for " | |
| 304 "${nodeToString(codePositionNode)}:\n" | |
| 305 "${DebugPrinter.prettyPrint(node)}")); | |
| 306 if (codePosition == null) return; | |
| 307 int codeLocation; | |
| 308 SourceLocation sourceLocation; | |
| 309 switch (codePositionKind) { | |
| 310 case CodePositionKind.START: | |
| 311 codeLocation = codePosition.startPosition; | |
| 312 break; | |
| 313 case CodePositionKind.CLOSING: | |
| 314 codeLocation = codePosition.closingPosition; | |
| 315 break; | |
| 316 case CodePositionKind.END: | |
| 317 codeLocation = codePosition.endPosition; | |
| 318 break; | |
| 319 } | |
| 320 switch (sourcePositionKind) { | |
| 321 case SourcePositionKind.START: | |
| 322 sourceLocation = sourceInformation.startPosition; | |
| 323 break; | |
| 324 case SourcePositionKind.CLOSING: | |
| 325 sourceLocation = sourceInformation.closingPosition; | |
| 326 break; | |
| 327 case SourcePositionKind.END: | |
| 328 sourceLocation = sourceInformation.endPosition; | |
| 329 break; | |
| 330 } | |
| 331 if (codeLocation != null && sourceLocation != null) { | |
| 332 sourceMapper.register(node, codeLocation, sourceLocation); | |
| 333 } | |
| 334 } | |
| 335 } | |
| 336 | |
| 337 @override | |
| 338 visitNode(js.Node node) { | |
| 339 SourceInformation sourceInformation = node.sourceInformation; | |
| 340 if (sourceInformation != null) { | |
| 341 /// Associates the left-most position of the JS code with the left-most | |
| 342 /// position of the Dart code. | |
| 343 apply(node, | |
| 344 node, CodePositionKind.START, | |
| 345 sourceInformation, SourcePositionKind.START); | |
| 346 } | |
| 347 visitChildren(node); | |
| 348 } | |
| 349 | |
| 350 @override | |
| 351 visitFun(js.Fun node) { | |
| 352 SourceInformation sourceInformation = node.sourceInformation; | |
| 353 if (sourceInformation != null) { | |
| 354 /// Associates the end brace of the JavaScript function with the end brace | |
| 355 /// of the Dart function (or the `;` in case of arrow notation). | |
| 356 apply(node, | |
| 357 node, CodePositionKind.CLOSING, | |
| 358 sourceInformation, SourcePositionKind.CLOSING); | |
| 359 } | |
| 360 | |
| 361 visitChildren(node); | |
| 362 } | |
| 363 | |
| 364 @override | |
| 365 visitExpressionStatement(js.ExpressionStatement node) { | |
| 366 visitChildren(node); | |
| 367 } | |
| 368 | |
| 369 @override | |
| 370 visitBinary(js.Binary node) { | |
| 371 visitChildren(node); | |
| 372 } | |
| 373 | |
| 374 @override | |
| 375 visitAccess(js.PropertyAccess node) { | |
| 376 visitChildren(node); | |
| 377 } | |
| 378 | |
| 379 @override | |
| 380 visitCall(js.Call node) { | |
| 381 SourceInformation sourceInformation = node.sourceInformation; | |
| 382 if (sourceInformation != null) { | |
| 383 if (node.target is js.PropertyAccess) { | |
| 384 js.PropertyAccess access = node.target; | |
| 385 js.Node target = access; | |
| 386 bool pureAccess = false; | |
| 387 while (target is js.PropertyAccess) { | |
| 388 js.PropertyAccess targetAccess = target; | |
| 389 if (targetAccess.receiver is js.VariableUse || | |
| 390 targetAccess.receiver is js.This) { | |
| 391 pureAccess = true; | |
| 392 break; | |
| 393 } else { | |
| 394 target = targetAccess.receiver; | |
| 395 } | |
| 396 } | |
| 397 if (pureAccess) { | |
| 398 // a.m() this.m() a.b.c.d.m() | |
| 399 // ^ ^ ^ | |
| 400 apply( | |
| 401 node, | |
| 402 node, | |
| 403 CodePositionKind.START, | |
| 404 sourceInformation, | |
| 405 SourcePositionKind.START); | |
| 406 } else { | |
| 407 // *.m() *.a.b.c.d.m() | |
| 408 // ^ ^ | |
| 409 apply( | |
| 410 node, | |
| 411 access.selector, | |
| 412 CodePositionKind.START, | |
| 413 sourceInformation, | |
| 414 SourcePositionKind.CLOSING); | |
| 415 } | |
| 416 } else if (node.target is js.VariableUse) { | |
| 417 // m() | |
| 418 // ^ | |
| 419 apply( | |
| 420 node, | |
| 421 node, | |
| 422 CodePositionKind.START, | |
| 423 sourceInformation, | |
| 424 SourcePositionKind.START); | |
| 425 } else if (node.target is js.Fun || node.target is js.New) { | |
| 426 // function(){}() new Function("...")() | |
| 427 // ^ ^ | |
| 428 apply( | |
| 429 node, | |
| 430 node.target, | |
| 431 CodePositionKind.END, | |
| 432 sourceInformation, | |
| 433 SourcePositionKind.CLOSING); | |
| 434 } else { | |
| 435 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
| 436 message: "Unexpected property access ${nodeToString(node)}:\n" | |
| 437 "${DebugPrinter.prettyPrint(node)}")); | |
| 438 // Don't know.... | |
| 439 apply( | |
| 440 node, | |
| 441 node, | |
| 442 CodePositionKind.START, | |
| 443 sourceInformation, | |
| 444 SourcePositionKind.START); | |
| 445 } | |
| 446 } | |
| 447 visitChildren(node); | |
| 448 } | 400 } |
| 449 | 401 |
| 450 @override | 402 @override |
| 451 void onPositions(js.Node node, | 403 void onPositions(js.Node node, |
| 452 int startPosition, | 404 int startPosition, |
| 453 int endPosition, | 405 int endPosition, |
| 454 int closingPosition) { | 406 int closingPosition) { |
| 455 codePositions.registerPositions( | 407 codePositionRecorder.registerPositions( |
| 456 node, startPosition, endPosition, closingPosition); | 408 node, startPosition, endPosition, closingPosition); |
| 457 } | 409 } |
| 458 } | 410 } |
| 411 | |
| 412 /// [TraceListener] that register [SourceLocation]s with a [SourceMapper]. | |
| 413 class PositionTraceListener extends TraceListener { | |
| 414 final SourceMapper sourceMapper; | |
| 415 | |
| 416 PositionTraceListener(this.sourceMapper); | |
| 417 | |
| 418 @override | |
| 419 void onStep(js.Node node, Offset offset, StepKind kind) { | |
| 420 SourceInformation sourceInformation = node.sourceInformation; | |
| 421 if (sourceInformation == null) return; | |
| 422 | |
| 423 SourcePositionKind sourcePositionKind = SourcePositionKind.START; | |
| 424 switch (kind) { | |
| 425 case StepKind.FUN: | |
| 426 sourcePositionKind = SourcePositionKind.INNER; | |
| 427 break; | |
| 428 case StepKind.CALL: | |
| 429 CallPosition callPosition = | |
| 430 CallPosition.getSemanticPositionForCall(node); | |
| 431 sourcePositionKind = callPosition.sourcePositionKind; | |
| 432 break; | |
| 433 case StepKind.NEW: | |
| 434 case StepKind.RETURN: | |
| 435 case StepKind.BREAK: | |
| 436 case StepKind.CONTINUE: | |
| 437 case StepKind.THROW: | |
| 438 case StepKind.EXPRESSION_STATEMENT: | |
| 439 case StepKind.IF_CONDITION: | |
| 440 case StepKind.FOR_INITIALIZER: | |
| 441 case StepKind.FOR_CONDITION: | |
| 442 case StepKind.FOR_UPDATE: | |
| 443 case StepKind.WHILE_CONDITION: | |
| 444 case StepKind.DO_CONDITION: | |
| 445 case StepKind.SWITCH_EXPRESSION: | |
| 446 break; | |
| 447 } | |
| 448 int codeLocation = offset.codeOffset; | |
| 449 SourceLocation sourceLocation = | |
| 450 getSourceLocation(sourceInformation, sourcePositionKind); | |
| 451 if (codeLocation != null && sourceLocation != null) { | |
| 452 sourceMapper.register(node, codeLocation, sourceLocation); | |
| 453 } | |
| 454 } | |
| 455 } | |
| 456 | |
| 457 /// The position of a [js.Call] node. | |
| 458 class CallPosition { | |
| 459 final js.Node node; | |
| 460 final CodePositionKind codePositionKind; | |
| 461 final SourcePositionKind sourcePositionKind; | |
| 462 | |
| 463 CallPosition(this.node, this.codePositionKind, this.sourcePositionKind); | |
| 464 | |
| 465 /// Computes the [CallPosition] for [node]. | |
| 466 static CallPosition getSemanticPositionForCall(js.Call node) { | |
| 467 if (node.target is js.PropertyAccess) { | |
| 468 js.PropertyAccess access = node.target; | |
| 469 js.Node target = access; | |
| 470 bool pureAccess = false; | |
| 471 while (target is js.PropertyAccess) { | |
| 472 js.PropertyAccess targetAccess = target; | |
| 473 if (targetAccess.receiver is js.VariableUse || | |
| 474 targetAccess.receiver is js.This) { | |
| 475 pureAccess = true; | |
| 476 break; | |
| 477 } else { | |
| 478 target = targetAccess.receiver; | |
| 479 } | |
| 480 } | |
| 481 if (pureAccess) { | |
| 482 // a.m() this.m() a.b.c.d.m() | |
| 483 // ^ ^ ^ | |
| 484 return new CallPosition( | |
| 485 node, | |
| 486 CodePositionKind.START, | |
| 487 SourcePositionKind.START); | |
| 488 } else { | |
| 489 // *.m() *.a.b.c.d.m() | |
| 490 // ^ ^ | |
| 491 return new CallPosition( | |
| 492 access.selector, | |
| 493 CodePositionKind.START, | |
| 494 SourcePositionKind.INNER); | |
| 495 } | |
| 496 } else if (node.target is js.VariableUse) { | |
| 497 // m() | |
| 498 // ^ | |
| 499 return new CallPosition( | |
| 500 node, | |
| 501 CodePositionKind.START, | |
| 502 SourcePositionKind.START); | |
| 503 } else if (node.target is js.Fun || node.target is js.New) { | |
| 504 // function(){}() new Function("...")() | |
| 505 // ^ ^ | |
| 506 return new CallPosition( | |
| 507 node.target, | |
| 508 CodePositionKind.END, | |
| 509 SourcePositionKind.INNER); | |
| 510 } else if (node.target is js.Binary || node.target is js.Call) { | |
| 511 // (0,a)() m()() | |
| 512 // ^ ^ | |
| 513 return new CallPosition( | |
| 514 node.target, | |
| 515 CodePositionKind.END, | |
| 516 SourcePositionKind.INNER); | |
| 517 } else { | |
| 518 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
| 519 message: "Unexpected property access ${nodeToString(node)}:\n" | |
| 520 "${DebugPrinter.prettyPrint(node)}")); | |
| 521 // Don't know.... | |
| 522 return new CallPosition( | |
| 523 node, | |
| 524 CodePositionKind.START, | |
| 525 SourcePositionKind.START); | |
| 526 } | |
| 527 } | |
| 528 } | |
| 529 | |
| 530 class Offset { | |
| 531 /// The offset of the enclosing statement relative to the beginning of the | |
| 532 /// file. | |
| 533 /// | |
| 534 /// For instance: | |
| 535 /// | |
| 536 /// foo().bar(baz()); | |
| 537 /// ^ // the statement offset of the `foo()` call | |
| 538 /// ^ // the statement offset of the `*.bar()` call | |
| 539 /// ^ // the statement offset of the `baz()` call | |
| 540 /// | |
| 541 final int statementOffset; | |
| 542 | |
| 543 /// The `subexpression` offset of the step. This is the (mostly) unique | |
| 544 /// offset relative to the beginning of the file, that identifies the | |
| 545 /// current of execution. | |
| 546 /// | |
| 547 /// For instance: | |
| 548 /// | |
| 549 /// foo().bar(baz()); | |
| 550 /// ^ // the subexpression offset of the `foo()` call | |
| 551 /// ^ // the subexpression offset of the `*.bar()` call | |
| 552 /// ^ // the subexpression offset of the `baz()` call | |
| 553 /// | |
| 554 /// Here, even though the JavaScript node for the `*.bar()` call contains | |
| 555 /// the `foo()` its execution is identified by the `bar` identifier more than | |
| 556 /// the foo identifier. | |
| 557 /// | |
| 558 final int codeOffset; | |
| 559 | |
| 560 /// The `left-to-right` offset of the step. This is a restricted version of | |
| 561 /// [subexpressionOffset] where offset by the execution order are always | |
|
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
A few notes:
- [subexpressionOffset] => [codeOffs
Johnni Winther
2016/01/25 09:58:55
Done.
| |
| 562 /// increasing within the same statement. | |
| 563 /// | |
| 564 /// For instance: | |
| 565 /// | |
| 566 /// foo().bar(baz()); | |
| 567 /// ^ // the left-to-right offset of the `foo()` call | |
| 568 /// ^ // the left-to-right offset of the `*.bar()` call | |
| 569 /// ^ // the left-to-right offset of the `baz()` call | |
| 570 /// | |
| 571 /// Here, `baz()` is executed before `foo()` be needs to use 'f' as its best | |
|
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
nit: "be needs" => "so we need to"?
Johnni Winther
2016/01/25 09:58:55
Done.
| |
| 572 /// position under the restriction. | |
| 573 /// | |
| 574 final int leftToRightOffset; | |
|
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
another idea for the name here: executionOrderOffs
Johnni Winther
2016/01/25 09:58:55
I find that equally confusing :(
| |
| 575 | |
| 576 Offset(this.statementOffset, this.leftToRightOffset, this.codeOffset); | |
| 577 | |
| 578 String toString() { | |
| 579 return 'Offset[statementOffset=$statementOffset,' | |
| 580 'leftToRightOffset=$leftToRightOffset,' | |
| 581 'codeOffset=$codeOffset]'; | |
| 582 } | |
| 583 } | |
| 584 | |
| 585 enum BranchKind { | |
| 586 CONDITION, | |
| 587 LOOP, | |
| 588 CATCH, | |
| 589 FINALLY, | |
| 590 CASE, | |
| 591 } | |
| 592 | |
| 593 enum StepKind { | |
| 594 FUN, | |
| 595 CALL, | |
| 596 NEW, | |
| 597 RETURN, | |
| 598 BREAK, | |
| 599 CONTINUE, | |
| 600 THROW, | |
| 601 EXPRESSION_STATEMENT, | |
| 602 IF_CONDITION, | |
| 603 FOR_INITIALIZER, | |
| 604 FOR_CONDITION, | |
| 605 FOR_UPDATE, | |
| 606 WHILE_CONDITION, | |
| 607 DO_CONDITION, | |
| 608 SWITCH_EXPRESSION, | |
| 609 } | |
| 610 | |
| 611 /// Listener for the [JavaScriptTracer]. | |
| 612 abstract class TraceListener { | |
| 613 /// Called before [root] node is procesed by the [JavaScriptTracer]. | |
| 614 void onStart(js.Node root) {} | |
| 615 | |
| 616 /// Called after [root] node has been procesed by the [JavaScriptTracer]. | |
| 617 void onEnd(js.Node root) {} | |
| 618 | |
| 619 /// Called when a branch of the given [kind] is started. [value] is provided | |
| 620 /// to distinguish true/false branches of [BranchKind.CONDITION] and cases of | |
| 621 /// [Branch.CASE]. | |
| 622 void pushBranch(BranchKind kind, [value]) {} | |
| 623 | |
| 624 /// Called when the current branch ends. | |
| 625 void popBranch() {} | |
| 626 | |
| 627 /// Called when [node] defines a step of the given [kind] at the given | |
| 628 /// [offset] when the generated JavaScript code. | |
| 629 void onStep(js.Node node, Offset offset, StepKind kind) {} | |
| 630 } | |
| 631 | |
| 632 /// Visitor that computes the [js.Node]s the are part of the JavaScript | |
| 633 /// steppable execution and thus needs source mapping locations. | |
| 634 class JavaScriptTracer extends js.BaseVisitor { | |
| 635 final CodePositionMap codePositions; | |
| 636 final List<TraceListener> listeners; | |
| 637 | |
| 638 /// The steps added by subexpressions. | |
| 639 List steps = []; | |
| 640 | |
| 641 /// The offset of the current statement. | |
| 642 int statementOffset; | |
| 643 | |
| 644 /// The current offset in left-to-right progression. | |
| 645 int leftToRightOffset; | |
| 646 | |
| 647 /// The offset of the surrounding statement, used for the first subexpression. | |
| 648 int offsetPosition; | |
| 649 | |
| 650 bool active; | |
| 651 | |
| 652 JavaScriptTracer(this.codePositions, | |
| 653 this.listeners, | |
| 654 {this.active: false}); | |
| 655 | |
| 656 void notifyStart(js.Node node) { | |
| 657 listeners.forEach((listener) => listener.onStart(node)); | |
| 658 } | |
| 659 | |
| 660 void notifyEnd(js.Node node) { | |
| 661 listeners.forEach((listener) => listener.onEnd(node)); | |
| 662 } | |
| 663 | |
| 664 void notifyPushBranch(BranchKind kind, [value]) { | |
| 665 if (active) { | |
| 666 listeners.forEach((listener) => listener.pushBranch(kind, value)); | |
| 667 } | |
| 668 } | |
| 669 | |
| 670 void notifyPopBranch() { | |
| 671 if (active) { | |
| 672 listeners.forEach((listener) => listener.popBranch()); | |
| 673 } | |
| 674 } | |
| 675 | |
| 676 void notifyStep(js.Node node, Offset offset, StepKind kind) { | |
| 677 if (active) { | |
| 678 listeners.forEach((listener) => listener.onStep(node, offset, kind)); | |
| 679 } | |
| 680 } | |
| 681 | |
| 682 void apply(js.Node node) { | |
| 683 notifyStart(node); | |
| 684 node.accept(this); | |
| 685 notifyEnd(node); | |
| 686 } | |
| 687 | |
| 688 @override | |
| 689 visitNode(js.Node node) { | |
| 690 node.visitChildren(this); | |
| 691 } | |
| 692 | |
| 693 visit(js.Node node, [BranchKind branch, value]) { | |
| 694 if (node != null) { | |
| 695 if (branch != null) { | |
| 696 notifyPushBranch(branch, value); | |
| 697 node.accept(this); | |
| 698 notifyPopBranch(); | |
| 699 } else { | |
| 700 node.accept(this); | |
| 701 } | |
| 702 } | |
| 703 } | |
| 704 | |
| 705 visitList(List<js.Node> nodeList) { | |
| 706 if (nodeList != null) { | |
| 707 for (js.Node node in nodeList) { | |
| 708 visit(node); | |
| 709 } | |
| 710 } | |
| 711 } | |
| 712 | |
| 713 @override | |
| 714 visitFun(js.Fun node) { | |
| 715 bool activeBefore = active; | |
| 716 if (!active) { | |
| 717 active = node.sourceInformation != null; | |
| 718 } | |
| 719 visit(node.body); | |
| 720 leftToRightOffset = statementOffset = | |
| 721 getSyntaxOffset(node, kind: CodePositionKind.CLOSING); | |
| 722 Offset offset = getOffsetForNode(node, statementOffset); | |
| 723 notifyStep(node, offset, StepKind.FUN); | |
| 724 active = activeBefore; | |
| 725 } | |
| 726 | |
| 727 @override | |
| 728 visitBlock(js.Block node) { | |
| 729 for (js.Statement statement in node.statements) { | |
| 730 visit(statement); | |
| 731 } | |
| 732 } | |
| 733 | |
| 734 int getSyntaxOffset(js.Node node, | |
| 735 {CodePositionKind kind: CodePositionKind.START}) { | |
| 736 CodePosition codePosition = codePositions[node]; | |
| 737 if (codePosition != null) { | |
| 738 return codePosition.getPosition(kind); | |
| 739 } | |
| 740 return null; | |
| 741 } | |
| 742 | |
| 743 visitSubexpression(js.Node parent, | |
| 744 js.Expression child, | |
| 745 int codeOffset, | |
| 746 StepKind kind) { | |
| 747 var oldSteps = steps; | |
| 748 steps = []; | |
| 749 offsetPosition = codeOffset; | |
| 750 visit(child); | |
| 751 if (steps.isEmpty) { | |
| 752 notifyStep(parent, | |
| 753 getOffsetForNode(parent, offsetPosition), | |
| 754 kind); | |
| 755 } | |
| 756 steps = oldSteps; | |
| 757 } | |
| 758 | |
| 759 @override | |
| 760 visitExpressionStatement(js.ExpressionStatement node) { | |
| 761 statementOffset = getSyntaxOffset(node); | |
| 762 visitSubexpression( | |
| 763 node, node.expression, statementOffset, | |
| 764 StepKind.EXPRESSION_STATEMENT); | |
| 765 statementOffset = null; | |
| 766 leftToRightOffset = null; | |
| 767 } | |
| 768 | |
| 769 @override | |
| 770 visitEmptyStatement(js.EmptyStatement node) {} | |
| 771 | |
| 772 @override | |
| 773 visitCall(js.Call node) { | |
| 774 visit(node.target); | |
| 775 int oldPosition = offsetPosition; | |
| 776 offsetPosition = null; | |
| 777 visitList(node.arguments); | |
| 778 offsetPosition = oldPosition; | |
| 779 CallPosition callPosition = | |
| 780 CallPosition.getSemanticPositionForCall(node); | |
| 781 js.Node positionNode = callPosition.node; | |
| 782 int callOffset = getSyntaxOffset( | |
| 783 positionNode, kind: callPosition.codePositionKind); | |
| 784 if (offsetPosition == null) { | |
| 785 offsetPosition = callOffset; | |
| 786 } | |
| 787 Offset offset = getOffsetForNode(positionNode, offsetPosition); | |
| 788 notifyStep(node, offset, StepKind.CALL); | |
| 789 steps.add(node); | |
| 790 offsetPosition = null; | |
| 791 } | |
| 792 | |
| 793 @override | |
| 794 visitNew(js.New node) { | |
| 795 visit(node.target); | |
| 796 visitList(node.arguments); | |
| 797 notifyStep( | |
| 798 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.NEW); | |
| 799 steps.add(node); | |
| 800 offsetPosition = null; | |
| 801 } | |
| 802 | |
| 803 @override | |
| 804 visitAccess(js.PropertyAccess node) { | |
| 805 visit(node.receiver); | |
| 806 visit(node.selector); | |
| 807 } | |
| 808 | |
| 809 @override | |
| 810 visitVariableUse(js.VariableUse node) {} | |
| 811 | |
| 812 @override | |
| 813 visitLiteralBool(js.LiteralBool node) {} | |
| 814 | |
| 815 @override | |
| 816 visitLiteralString(js.LiteralString node) {} | |
| 817 | |
| 818 @override | |
| 819 visitLiteralNumber(js.LiteralNumber node) {} | |
| 820 | |
| 821 @override | |
| 822 visitLiteralNull(js.LiteralNull node) {} | |
| 823 | |
| 824 @override | |
| 825 visitName(js.Name node) {} | |
| 826 | |
| 827 @override | |
| 828 visitVariableDeclarationList(js.VariableDeclarationList node) { | |
| 829 visitList(node.declarations); | |
| 830 } | |
| 831 | |
| 832 @override | |
| 833 visitVariableDeclaration(js.VariableDeclaration node) {} | |
| 834 | |
| 835 @override | |
| 836 visitVariableInitialization(js.VariableInitialization node) { | |
| 837 visit(node.leftHandSide); | |
| 838 visit(node.value); | |
| 839 } | |
| 840 | |
| 841 @override | |
| 842 visitAssignment(js.Assignment node) { | |
| 843 visit(node.leftHandSide); | |
| 844 visit(node.value); | |
| 845 } | |
| 846 | |
| 847 @override | |
| 848 visitIf(js.If node) { | |
| 849 statementOffset = getSyntaxOffset(node); | |
| 850 visitSubexpression(node, node.condition, statementOffset, | |
| 851 StepKind.IF_CONDITION); | |
| 852 statementOffset = null; | |
| 853 visit(node.then, BranchKind.CONDITION, true); | |
| 854 visit(node.otherwise, BranchKind.CONDITION, false); | |
| 855 } | |
| 856 | |
| 857 @override | |
| 858 visitFor(js.For node) { | |
| 859 int offset = statementOffset = getSyntaxOffset(node); | |
| 860 statementOffset = offset; | |
| 861 leftToRightOffset = null; | |
| 862 if (node.init != null) { | |
| 863 visitSubexpression(node, node.init, getSyntaxOffset(node), | |
| 864 StepKind.FOR_INITIALIZER); | |
| 865 } | |
| 866 | |
| 867 if (node.condition != null) { | |
| 868 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
| 869 StepKind.FOR_CONDITION); | |
| 870 } | |
| 871 | |
| 872 notifyPushBranch(BranchKind.LOOP); | |
| 873 visit(node.body); | |
| 874 | |
| 875 statementOffset = offset; | |
| 876 if (node.update != null) { | |
| 877 visitSubexpression(node, node.update, getSyntaxOffset(node.update), | |
| 878 StepKind.FOR_UPDATE); | |
| 879 } | |
| 880 | |
| 881 notifyPopBranch(); | |
| 882 } | |
| 883 | |
| 884 @override | |
| 885 visitWhile(js.While node) { | |
| 886 statementOffset = getSyntaxOffset(node); | |
| 887 if (node.condition != null) { | |
| 888 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
| 889 StepKind.WHILE_CONDITION); | |
| 890 } | |
| 891 statementOffset = null; | |
| 892 leftToRightOffset = null; | |
| 893 | |
| 894 visit(node.body, BranchKind.LOOP); | |
| 895 } | |
| 896 | |
| 897 @override | |
| 898 visitDo(js.Do node) { | |
| 899 statementOffset = getSyntaxOffset(node); | |
| 900 visit(node.body); | |
| 901 if (node.condition != null) { | |
| 902 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
| 903 StepKind.DO_CONDITION); | |
| 904 } | |
| 905 statementOffset = null; | |
| 906 leftToRightOffset = null; | |
| 907 } | |
| 908 | |
| 909 @override | |
| 910 visitBinary(js.Binary node) { | |
| 911 visit(node.left); | |
| 912 visit(node.right); | |
| 913 } | |
| 914 | |
| 915 @override | |
| 916 visitThis(js.This node) {} | |
| 917 | |
| 918 @override | |
| 919 visitReturn(js.Return node) { | |
| 920 statementOffset = getSyntaxOffset(node); | |
| 921 visit(node.value); | |
| 922 notifyStep( | |
| 923 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.RETURN); | |
| 924 statementOffset = null; | |
| 925 leftToRightOffset = null; | |
| 926 } | |
| 927 | |
| 928 @override | |
| 929 visitThrow(js.Throw node) { | |
| 930 statementOffset = getSyntaxOffset(node); | |
| 931 visit(node.expression); | |
| 932 notifyStep( | |
| 933 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.THROW); | |
| 934 statementOffset = null; | |
| 935 leftToRightOffset = null; | |
| 936 } | |
| 937 | |
| 938 @override | |
| 939 visitContinue(js.Continue node) { | |
| 940 statementOffset = getSyntaxOffset(node); | |
| 941 notifyStep( | |
| 942 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.CONTINUE); | |
| 943 statementOffset = null; | |
| 944 leftToRightOffset = null; | |
| 945 } | |
| 946 | |
| 947 @override | |
| 948 visitBreak(js.Break node) { | |
| 949 statementOffset = getSyntaxOffset(node); | |
| 950 notifyStep( | |
| 951 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.BREAK); | |
| 952 statementOffset = null; | |
| 953 leftToRightOffset = null; | |
| 954 } | |
| 955 | |
| 956 @override | |
| 957 visitTry(js.Try node) { | |
| 958 visit(node.body); | |
| 959 visit(node.catchPart, BranchKind.CATCH); | |
| 960 visit(node.finallyPart, BranchKind.FINALLY); | |
| 961 } | |
| 962 | |
| 963 @override | |
| 964 visitCatch(js.Catch node) { | |
| 965 visit(node.body); | |
| 966 } | |
| 967 | |
| 968 @override | |
| 969 visitConditional(js.Conditional node) { | |
| 970 visit(node.condition); | |
| 971 visit(node.then, BranchKind.CONDITION, true); | |
| 972 visit(node.otherwise, BranchKind.CONDITION, false); | |
| 973 } | |
| 974 | |
| 975 @override | |
| 976 visitPrefix(js.Prefix node) { | |
| 977 visit(node.argument); | |
| 978 } | |
| 979 | |
| 980 @override | |
| 981 visitPostfix(js.Postfix node) { | |
| 982 visit(node.argument); | |
| 983 } | |
| 984 | |
| 985 @override | |
| 986 visitObjectInitializer(js.ObjectInitializer node) { | |
| 987 visitList(node.properties); | |
| 988 } | |
| 989 | |
| 990 @override | |
| 991 visitProperty(js.Property node) { | |
| 992 visit(node.name); | |
| 993 visit(node.value); | |
| 994 } | |
| 995 | |
| 996 @override | |
| 997 visitRegExpLiteral(js.RegExpLiteral node) {} | |
| 998 | |
| 999 @override | |
| 1000 visitSwitch(js.Switch node) { | |
| 1001 statementOffset = getSyntaxOffset(node); | |
| 1002 visitSubexpression(node, node.key, getSyntaxOffset(node), | |
| 1003 StepKind.SWITCH_EXPRESSION); | |
| 1004 statementOffset = null; | |
| 1005 leftToRightOffset = null; | |
| 1006 for (int i = 0; i < node.cases.length; i++) { | |
| 1007 visit(node.cases[i], BranchKind.CASE, i); | |
| 1008 } | |
| 1009 } | |
| 1010 | |
| 1011 @override | |
| 1012 visitCase(js.Case node) { | |
| 1013 visit(node.expression); | |
| 1014 visit(node.body); | |
| 1015 } | |
| 1016 | |
| 1017 @override | |
| 1018 visitDefault(js.Default node) { | |
| 1019 visit(node.body); | |
| 1020 } | |
| 1021 | |
| 1022 @override | |
| 1023 visitArrayInitializer(js.ArrayInitializer node) { | |
| 1024 visitList(node.elements); | |
| 1025 } | |
| 1026 | |
| 1027 @override | |
| 1028 visitArrayHole(js.ArrayHole node) {} | |
| 1029 | |
| 1030 @override | |
| 1031 visitLabeledStatement(js.LabeledStatement node) { | |
| 1032 statementOffset = getSyntaxOffset(node); | |
| 1033 visit(node.body); | |
| 1034 statementOffset = null; | |
| 1035 } | |
| 1036 | |
| 1037 Offset getOffsetForNode(js.Node node, int codeOffset) { | |
| 1038 if (codeOffset == null) { | |
| 1039 CodePosition codePosition = codePositions[node]; | |
| 1040 if (codePosition != null) { | |
| 1041 codeOffset = codePosition.startPosition; | |
| 1042 } | |
| 1043 } | |
| 1044 if (leftToRightOffset != null && leftToRightOffset < codeOffset) { | |
| 1045 leftToRightOffset = codeOffset; | |
| 1046 } | |
| 1047 if (leftToRightOffset == null) { | |
| 1048 leftToRightOffset = statementOffset; | |
| 1049 } | |
| 1050 return new Offset(statementOffset, leftToRightOffset, codeOffset); | |
| 1051 } | |
| 1052 } | |
| 1053 | |
| 1054 | |
| 1055 class Coverage { | |
| 1056 Set<js.Node> _nodesWithInfo = new Set<js.Node>(); | |
| 1057 int _nodesWithInfoCount = 0; | |
| 1058 Set<js.Node> _nodesWithoutInfo = new Set<js.Node>(); | |
| 1059 int _nodesWithoutInfoCount = 0; | |
| 1060 Map<Type, int> _nodesWithoutInfoCountByType = <Type, int>{}; | |
| 1061 Set<js.Node> _nodesWithoutOffset = new Set<js.Node>(); | |
| 1062 int _nodesWithoutOffsetCount = 0; | |
| 1063 | |
| 1064 void registerNodeWithInfo(js.Node node) { | |
| 1065 _nodesWithInfo.add(node); | |
| 1066 } | |
| 1067 | |
| 1068 void registerNodeWithoutInfo(js.Node node) { | |
| 1069 _nodesWithoutInfo.add(node); | |
| 1070 } | |
| 1071 | |
| 1072 void registerNodesWithoutOffset(js.Node node) { | |
| 1073 _nodesWithoutOffset.add(node); | |
| 1074 } | |
| 1075 | |
| 1076 void collapse() { | |
| 1077 _nodesWithInfoCount += _nodesWithInfo.length; | |
| 1078 _nodesWithInfo.clear(); | |
| 1079 _nodesWithoutOffsetCount += _nodesWithoutOffset.length; | |
| 1080 _nodesWithoutOffset.clear(); | |
| 1081 | |
| 1082 _nodesWithoutInfoCount += _nodesWithoutInfo.length; | |
| 1083 for (js.Node node in _nodesWithoutInfo) { | |
| 1084 if (node is js.ExpressionStatement) { | |
| 1085 _nodesWithoutInfoCountByType.putIfAbsent( | |
| 1086 node.expression.runtimeType, () => 0); | |
| 1087 _nodesWithoutInfoCountByType[node.expression.runtimeType]++; | |
| 1088 } else { | |
| 1089 _nodesWithoutInfoCountByType.putIfAbsent( | |
| 1090 node.runtimeType, () => 0); | |
| 1091 _nodesWithoutInfoCountByType[node.runtimeType]++; | |
| 1092 } | |
| 1093 } | |
| 1094 _nodesWithoutInfo.clear(); | |
| 1095 } | |
| 1096 | |
| 1097 String getCoverageReport() { | |
| 1098 collapse(); | |
| 1099 StringBuffer sb = new StringBuffer(); | |
| 1100 int total = _nodesWithInfoCount + _nodesWithoutInfoCount; | |
| 1101 if (total > 0) { | |
| 1102 sb.write(_nodesWithoutInfoCount); | |
| 1103 sb.write('/'); | |
| 1104 sb.write(total); | |
| 1105 sb.write(' ('); | |
| 1106 sb.write((100.0 * _nodesWithInfoCount / total).toStringAsFixed(2)); | |
| 1107 sb.write('%) nodes with info.'); | |
| 1108 } else { | |
| 1109 sb.write('No nodes.'); | |
| 1110 } | |
| 1111 if (_nodesWithoutOffsetCount > 0) { | |
| 1112 sb.write(' '); | |
| 1113 sb.write(_nodesWithoutOffsetCount); | |
| 1114 sb.write(' node'); | |
| 1115 if (_nodesWithoutOffsetCount > 1) { | |
| 1116 sb.write('s'); | |
| 1117 } | |
| 1118 sb.write(' without offset.'); | |
| 1119 } | |
| 1120 if (_nodesWithoutInfoCount > 0) { | |
| 1121 sb.write('\nNodes without info ('); | |
| 1122 sb.write(_nodesWithoutInfoCount); | |
| 1123 sb.write(') by runtime type:'); | |
| 1124 _nodesWithoutInfoCountByType.forEach((Type type, int count) { | |
| 1125 sb.write('\n '); | |
| 1126 sb.write(count); | |
| 1127 sb.write(' '); | |
| 1128 sb.write(type); | |
| 1129 sb.write(' node'); | |
| 1130 if (count > 1) { | |
| 1131 sb.write('s'); | |
| 1132 } | |
| 1133 }); | |
| 1134 sb.write('\n'); | |
| 1135 } | |
| 1136 return sb.toString(); | |
| 1137 } | |
| 1138 | |
| 1139 String toString() => getCoverageReport(); | |
| 1140 } | |
| 1141 | |
| 1142 /// [TraceListener] that registers [onStep] callbacks with [coverage]. | |
| 1143 class CoverageListener extends TraceListener { | |
| 1144 final Coverage coverage; | |
| 1145 | |
| 1146 CoverageListener(this.coverage); | |
| 1147 | |
| 1148 @override | |
| 1149 void onStep(js.Node node, Offset offset, StepKind kind) { | |
| 1150 SourceInformation sourceInformation = node.sourceInformation; | |
| 1151 if (sourceInformation != null) { | |
| 1152 coverage.registerNodeWithInfo(node); | |
| 1153 } else { | |
| 1154 coverage.registerNodeWithoutInfo(node); | |
| 1155 } | |
| 1156 } | |
| 1157 | |
| 1158 @override | |
| 1159 void onEnd(js.Node node) { | |
| 1160 coverage.collapse(); | |
| 1161 } | |
| 1162 } | |
| 1163 | |
| 1164 /// [CodePositionMap] that registers calls with [Coverage]. | |
| 1165 class CodePositionCoverage implements CodePositionMap { | |
| 1166 final CodePositionMap codePositions; | |
| 1167 final Coverage coverage; | |
| 1168 | |
| 1169 CodePositionCoverage(this.codePositions, this.coverage); | |
| 1170 | |
| 1171 @override | |
| 1172 CodePosition operator [](js.Node node) { | |
| 1173 CodePosition codePosition = codePositions[node]; | |
| 1174 if (codePosition == null) { | |
| 1175 coverage.registerNodesWithoutOffset(node); | |
| 1176 } | |
| 1177 return codePosition; | |
| 1178 } | |
| 1179 } | |
| OLD | NEW |