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