| Index: pkg/compiler/lib/src/io/position_information.dart
 | 
| diff --git a/pkg/compiler/lib/src/io/position_information.dart b/pkg/compiler/lib/src/io/position_information.dart
 | 
| index 0e384c32bc3fd51ef76fc6950110ef51381e4dff..2ef185be452f55817550c3ee125510c01b23679c 100644
 | 
| --- a/pkg/compiler/lib/src/io/position_information.dart
 | 
| +++ b/pkg/compiler/lib/src/io/position_information.dart
 | 
| @@ -18,6 +18,8 @@ import '../tree/tree.dart' show
 | 
|      Node,
 | 
|      Send;
 | 
|  
 | 
| +import 'code_output.dart' show
 | 
| +    CodeBuffer;
 | 
|  import 'source_file.dart';
 | 
|  import 'source_information.dart';
 | 
|  
 | 
| @@ -113,6 +115,32 @@ class PositionSourceInformationStrategy
 | 
|    SourceInformationProcessor createProcessor(SourceMapper mapper) {
 | 
|      return new PositionSourceInformationProcessor(mapper);
 | 
|    }
 | 
| +
 | 
| +  @override
 | 
| +  void onComplete() {}
 | 
| +
 | 
| +  @override
 | 
| +  SourceInformation buildSourceMappedMarker() {
 | 
| +    return const SourceMappedMarker();
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Marker used to tag the root nodes of source-mapped code.
 | 
| +///
 | 
| +/// This is needed to be able to distinguish JavaScript nodes that shouldn't
 | 
| +/// have source locations (like the premable) from the nodes that should
 | 
| +/// (like functions compiled from Dart code).
 | 
| +class SourceMappedMarker extends SourceInformation {
 | 
| +  const SourceMappedMarker();
 | 
| +
 | 
| +  @override
 | 
| +  String get shortText => '';
 | 
| +
 | 
| +  @override
 | 
| +  List<SourceLocation> get sourceLocations => const <SourceLocation>[];
 | 
| +
 | 
| +  @override
 | 
| +  SourceSpan get sourceSpan => new SourceSpan(null, null, null);
 | 
|  }
 | 
|  
 | 
|  /// [SourceInformationBuilder] that generates [PositionSourceInformation].
 | 
| @@ -225,10 +253,31 @@ class CodePosition {
 | 
|    final int closingPosition;
 | 
|  
 | 
|    CodePosition(this.startPosition, this.endPosition, this.closingPosition);
 | 
| +
 | 
| +  int getPosition(CodePositionKind kind) {
 | 
| +    switch (kind) {
 | 
| +      case CodePositionKind.START:
 | 
| +        return startPosition;
 | 
| +      case CodePositionKind.END:
 | 
| +        return endPosition;
 | 
| +      case CodePositionKind.CLOSING:
 | 
| +        return closingPosition;
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  String toString() {
 | 
| +    return 'CodePosition(start=$startPosition,'
 | 
| +           'end=$endPosition,closing=$closingPosition)';
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// A map from a [js.Node] to its [CodePosition].
 | 
| +abstract class CodePositionMap {
 | 
| +  CodePosition operator [](js.Node node);
 | 
|  }
 | 
|  
 | 
|  /// Registry for mapping [js.Node]s to their [CodePosition].
 | 
| -class CodePositionRecorder {
 | 
| +class CodePositionRecorder implements CodePositionMap {
 | 
|    Map<js.Node, CodePosition> _codePositionMap =
 | 
|        new Map<js.Node, CodePosition>.identity();
 | 
|  
 | 
| @@ -247,212 +296,884 @@ class CodePositionRecorder {
 | 
|    CodePosition operator [](js.Node node) => _codePositionMap[node];
 | 
|  }
 | 
|  
 | 
| +/// Enum values for the part of a Dart node used for the source location offset.
 | 
|  enum SourcePositionKind {
 | 
| +  /// The source mapping should point to the start of the Dart node.
 | 
| +  ///
 | 
| +  /// For instance the first '(' for the `(*)()` call and 'f' of both the
 | 
| +  /// `foo()` and the `*.bar()` call:
 | 
| +  ///
 | 
| +  ///     (foo().bar())()
 | 
| +  ///     ^                       // the start of the `(*)()` node
 | 
| +  ///      ^                      // the start of the `foo()` node
 | 
| +  ///      ^                      // the start of the `*.bar()` node
 | 
| +  ///
 | 
|    START,
 | 
| -  CLOSING,
 | 
| -  END,
 | 
| +
 | 
| +  /// The source mapping should point an inner position of the Dart node.
 | 
| +  ///
 | 
| +  /// For instance the second '(' of the `(*)()` call, the 'f' of the `foo()`
 | 
| +  /// call and the 'b' of the `*.bar()` call:
 | 
| +  ///
 | 
| +  ///     (foo().bar())()
 | 
| +  ///                  ^          // the inner position of the `(*)()` node
 | 
| +  ///      ^                      // the inner position of the `foo()` node
 | 
| +  ///            ^                // the inner position of the `*.bar()` node
 | 
| +  ///
 | 
| +  /// For function expressions the inner position is the closing brace or the
 | 
| +  /// arrow:
 | 
| +  ///
 | 
| +  ///     foo() => () {}
 | 
| +  ///           ^                 // the inner position of the 'foo' function
 | 
| +  ///                  ^          // the inner position of the closure
 | 
| +  ///
 | 
| +  INNER,
 | 
|  }
 | 
|  
 | 
| +SourceLocation getSourceLocation(
 | 
| +    SourceInformation sourceInformation,
 | 
| +    [SourcePositionKind sourcePositionKind = SourcePositionKind.START]) {
 | 
| +  if (sourceInformation == null) return null;
 | 
| +  switch (sourcePositionKind) {
 | 
| +    case SourcePositionKind.START:
 | 
| +      return sourceInformation.startPosition;
 | 
| +    case SourcePositionKind.INNER:
 | 
| +      return sourceInformation.closingPosition;
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +/// Enum values for the part of the JavaScript node used for the JavaScript
 | 
| +/// code offset of a source mapping.
 | 
|  enum CodePositionKind {
 | 
| +  /// The source mapping is put on left-most offset of the node.
 | 
| +  ///
 | 
| +  /// For instance on the 'f' of a function or 'r' of a return statement:
 | 
| +  ///
 | 
| +  ///     foo: function() { return 0; }
 | 
| +  ///          ^                              // the function start position
 | 
| +  ///                       ^                 // the return start position
 | 
|    START,
 | 
| +
 | 
| +  /// The source mapping is put on the closing token.
 | 
| +  ///
 | 
| +  /// For instance on the '}' of a function or the ';' of a return statement:
 | 
| +  ///
 | 
| +  ///     foo: function() { return 0; }
 | 
| +  ///                                 ^       // the function closing position
 | 
| +  ///                               ^         // the return closing position
 | 
| +  ///
 | 
|    CLOSING,
 | 
| +
 | 
| +  /// The source mapping is put at the end of the code for the node.
 | 
| +  ///
 | 
| +  /// For instance after '}' of a function or after the ';' of a return
 | 
| +  /// statement:
 | 
| +  ///
 | 
| +  ///     foo: function() { return 0; }
 | 
| +  ///                                  ^       // the function end position
 | 
| +  ///                                ^         // the return end position
 | 
| +  ///
 | 
|    END,
 | 
|  }
 | 
|  
 | 
|  /// Processor that associates [SourceLocation]s from [SourceInformation] on
 | 
|  /// [js.Node]s with the target offsets in a [SourceMapper].
 | 
| -class PositionSourceInformationProcessor
 | 
| -    extends js.BaseVisitor implements SourceInformationProcessor {
 | 
| -  final CodePositionRecorder codePositions = new CodePositionRecorder();
 | 
| -  final SourceMapper sourceMapper;
 | 
| +class PositionSourceInformationProcessor implements SourceInformationProcessor {
 | 
| +  final CodePositionRecorder codePositionRecorder = new CodePositionRecorder();
 | 
| +  CodePositionMap codePositionMap;
 | 
| +  List<TraceListener> traceListeners;
 | 
|  
 | 
| -  PositionSourceInformationProcessor(this.sourceMapper);
 | 
| +  PositionSourceInformationProcessor(
 | 
| +      SourceMapper sourceMapper,
 | 
| +      [Coverage coverage]) {
 | 
| +    codePositionMap = coverage != null
 | 
| +              ? new CodePositionCoverage(codePositionRecorder, coverage)
 | 
| +              : codePositionRecorder;
 | 
| +    traceListeners = [new PositionTraceListener(sourceMapper)];
 | 
| +    if (coverage != null) {
 | 
| +      traceListeners.add(new CoverageListener(coverage));
 | 
| +    }
 | 
| +  }
 | 
|  
 | 
| -  void process(js.Node node) {
 | 
| -    node.accept(this);
 | 
| +  void process(js.Node node, CodeBuffer codeBuffer) {
 | 
| +    new JavaScriptTracer(codePositionMap, traceListeners).apply(node);
 | 
|    }
 | 
|  
 | 
| -  void visitChildren(js.Node node) {
 | 
| -    node.visitChildren(this);
 | 
| +  @override
 | 
| +  void onPositions(js.Node node,
 | 
| +                   int startPosition,
 | 
| +                   int endPosition,
 | 
| +                   int closingPosition) {
 | 
| +    codePositionRecorder.registerPositions(
 | 
| +        node, startPosition, endPosition, closingPosition);
 | 
|    }
 | 
| +}
 | 
| +
 | 
| +/// [TraceListener] that register [SourceLocation]s with a [SourceMapper].
 | 
| +class PositionTraceListener extends TraceListener {
 | 
| +  final SourceMapper sourceMapper;
 | 
| +
 | 
| +  PositionTraceListener(this.sourceMapper);
 | 
| +
 | 
| +  @override
 | 
| +  void onStep(js.Node node, Offset offset, StepKind kind) {
 | 
| +    SourceInformation sourceInformation = node.sourceInformation;
 | 
| +    if (sourceInformation == null) return;
 | 
|  
 | 
| -  CodePosition getCodePosition(js.Node node) {
 | 
| -    return codePositions[node];
 | 
| +    SourcePositionKind sourcePositionKind = SourcePositionKind.START;
 | 
| +    switch (kind) {
 | 
| +      case StepKind.FUN:
 | 
| +        sourcePositionKind = SourcePositionKind.INNER;
 | 
| +        break;
 | 
| +      case StepKind.CALL:
 | 
| +        CallPosition callPosition =
 | 
| +            CallPosition.getSemanticPositionForCall(node);
 | 
| +        sourcePositionKind = callPosition.sourcePositionKind;
 | 
| +        break;
 | 
| +      case StepKind.NEW:
 | 
| +      case StepKind.RETURN:
 | 
| +      case StepKind.BREAK:
 | 
| +      case StepKind.CONTINUE:
 | 
| +      case StepKind.THROW:
 | 
| +      case StepKind.EXPRESSION_STATEMENT:
 | 
| +      case StepKind.IF_CONDITION:
 | 
| +      case StepKind.FOR_INITIALIZER:
 | 
| +      case StepKind.FOR_CONDITION:
 | 
| +      case StepKind.FOR_UPDATE:
 | 
| +      case StepKind.WHILE_CONDITION:
 | 
| +      case StepKind.DO_CONDITION:
 | 
| +      case StepKind.SWITCH_EXPRESSION:
 | 
| +        break;
 | 
| +    }
 | 
| +    int codeLocation = offset.subexpressionOffset;
 | 
| +    SourceLocation sourceLocation =
 | 
| +        getSourceLocation(sourceInformation, sourcePositionKind);
 | 
| +    if (codeLocation != null && sourceLocation != null) {
 | 
| +      sourceMapper.register(node, codeLocation, sourceLocation);
 | 
| +    }
 | 
|    }
 | 
| +}
 | 
|  
 | 
| -  /// Associates [sourceInformation] with the JavaScript [node].
 | 
| -  ///
 | 
| -  /// The offset into the JavaScript code is computed by pulling the
 | 
| -  /// [codePositionKind] from the code positions associated with
 | 
| -  /// [codePositionNode].
 | 
| -  ///
 | 
| -  /// The mapped Dart source location is computed by pulling the
 | 
| -  /// [sourcePositionKind] source location from [sourceInformation].
 | 
| -  void apply(js.Node node,
 | 
| -             js.Node codePositionNode,
 | 
| -             CodePositionKind codePositionKind,
 | 
| -             SourceInformation sourceInformation,
 | 
| -             SourcePositionKind sourcePositionKind) {
 | 
| -    if (sourceInformation != null) {
 | 
| -      CodePosition codePosition = getCodePosition(codePositionNode);
 | 
| -      // We should always have recorded the needed code positions.
 | 
| -      assert(invariant(
 | 
| -          NO_LOCATION_SPANNABLE,
 | 
| -          codePosition != null,
 | 
| -          message:
 | 
| -            "Code position missing for "
 | 
| -            "${nodeToString(codePositionNode)}:\n"
 | 
| -            "${DebugPrinter.prettyPrint(node)}"));
 | 
| -      if (codePosition == null) return;
 | 
| -      int codeLocation;
 | 
| -      SourceLocation sourceLocation;
 | 
| -      switch (codePositionKind) {
 | 
| -        case CodePositionKind.START:
 | 
| -          codeLocation = codePosition.startPosition;
 | 
| -          break;
 | 
| -        case CodePositionKind.CLOSING:
 | 
| -          codeLocation = codePosition.closingPosition;
 | 
| -          break;
 | 
| -        case CodePositionKind.END:
 | 
| -          codeLocation = codePosition.endPosition;
 | 
| -          break;
 | 
| -      }
 | 
| -      switch (sourcePositionKind) {
 | 
| -        case SourcePositionKind.START:
 | 
| -          sourceLocation = sourceInformation.startPosition;
 | 
| -          break;
 | 
| -        case SourcePositionKind.CLOSING:
 | 
| -          sourceLocation = sourceInformation.closingPosition;
 | 
| -          break;
 | 
| -        case SourcePositionKind.END:
 | 
| -          sourceLocation = sourceInformation.endPosition;
 | 
| +/// The position of a [js.Call] node.
 | 
| +class CallPosition {
 | 
| +  final js.Node node;
 | 
| +  final CodePositionKind codePositionKind;
 | 
| +  final SourcePositionKind sourcePositionKind;
 | 
| +
 | 
| +  CallPosition(this.node, this.codePositionKind, this.sourcePositionKind);
 | 
| +
 | 
| +  /// Computes the [CallPosition] for [node].
 | 
| +  static CallPosition getSemanticPositionForCall(js.Call node) {
 | 
| +    if (node.target is js.PropertyAccess) {
 | 
| +      js.PropertyAccess access = node.target;
 | 
| +      js.Node target = access;
 | 
| +      bool pureAccess = false;
 | 
| +      while (target is js.PropertyAccess) {
 | 
| +        js.PropertyAccess targetAccess = target;
 | 
| +        if (targetAccess.receiver is js.VariableUse ||
 | 
| +            targetAccess.receiver is js.This) {
 | 
| +          pureAccess = true;
 | 
|            break;
 | 
| +        } else {
 | 
| +          target = targetAccess.receiver;
 | 
| +        }
 | 
|        }
 | 
| -      if (codeLocation != null && sourceLocation != null) {
 | 
| -        sourceMapper.register(node, codeLocation, sourceLocation);
 | 
| +      if (pureAccess) {
 | 
| +        // a.m()   this.m()  a.b.c.d.m()
 | 
| +        // ^       ^         ^
 | 
| +        return new CallPosition(
 | 
| +            node,
 | 
| +            CodePositionKind.START,
 | 
| +            SourcePositionKind.START);
 | 
| +      } else {
 | 
| +        // *.m()  *.a.b.c.d.m()
 | 
| +        //   ^              ^
 | 
| +        return new CallPosition(
 | 
| +            access.selector,
 | 
| +            CodePositionKind.START,
 | 
| +            SourcePositionKind.INNER);
 | 
|        }
 | 
| +    } else if (node.target is js.VariableUse) {
 | 
| +      // m()
 | 
| +      // ^
 | 
| +      return new CallPosition(
 | 
| +          node,
 | 
| +          CodePositionKind.START,
 | 
| +          SourcePositionKind.START);
 | 
| +    } else if (node.target is js.Fun || node.target is js.New) {
 | 
| +      // function(){}()  new Function("...")()
 | 
| +      //             ^                      ^
 | 
| +      return new CallPosition(
 | 
| +          node.target,
 | 
| +          CodePositionKind.END,
 | 
| +          SourcePositionKind.INNER);
 | 
| +    } else if (node.target is js.Binary || node.target is js.Call) {
 | 
| +      // (0,a)()   m()()
 | 
| +      //      ^       ^
 | 
| +      return new CallPosition(
 | 
| +          node.target,
 | 
| +          CodePositionKind.END,
 | 
| +          SourcePositionKind.INNER);
 | 
| +    } else {
 | 
| +      assert(invariant(NO_LOCATION_SPANNABLE, false,
 | 
| +          message: "Unexpected property access ${nodeToString(node)}:\n"
 | 
| +                   "${DebugPrinter.prettyPrint(node)}"));
 | 
| +      // Don't know....
 | 
| +      return new CallPosition(
 | 
| +          node,
 | 
| +          CodePositionKind.START,
 | 
| +          SourcePositionKind.START);
 | 
|      }
 | 
|    }
 | 
| +}
 | 
| +
 | 
| +class Offset {
 | 
| +  /// The offset of the enclosing statement relative to the beginning of the
 | 
| +  /// file.
 | 
| +  ///
 | 
| +  /// For instance:
 | 
| +  ///
 | 
| +  ///     foo().bar(baz());
 | 
| +  ///     ^                  // the statement offset of the `foo()` call
 | 
| +  ///     ^                  // the statement offset of the `*.bar()` call
 | 
| +  ///     ^                  // the statement offset of the `baz()` call
 | 
| +  ///
 | 
| +  final int statementOffset;
 | 
| +
 | 
| +  /// The `subexpression` offset of the step. This is the (mostly) unique
 | 
| +  /// offset relative to the beginning of the file, that identifies the
 | 
| +  /// current of execution.
 | 
| +  ///
 | 
| +  /// For instance:
 | 
| +  ///
 | 
| +  ///     foo().bar(baz());
 | 
| +  ///     ^                   // the subexpression offset of the `foo()` call
 | 
| +  ///           ^             // the subexpression offset of the `*.bar()` call
 | 
| +  ///               ^         // the subexpression offset of the `baz()` call
 | 
| +  ///
 | 
| +  /// Here, even though the JavaScript node for the `*.bar()` call contains
 | 
| +  /// the `foo()` its execution is identified by the `bar` identifier more than
 | 
| +  /// the foo identifier.
 | 
| +  ///
 | 
| +  final int subexpressionOffset;
 | 
| +
 | 
| +  /// The `left-to-right` offset of the step. This is like [subexpressionOffset]
 | 
| +  /// bute restricted so that the offset of each subexpression in execution
 | 
| +  /// order is monotonically increasing.
 | 
| +  ///
 | 
| +  /// For instance:
 | 
| +  ///
 | 
| +  ///     foo().bar(baz());
 | 
| +  ///     ^                   // the left-to-right offset of the `foo()` call
 | 
| +  ///           ^             // the left-to-right offset of the `*.bar()` call
 | 
| +  ///     ^                   // the left-to-right offset of the `baz()` call
 | 
| +  ///
 | 
| +  /// Here, `baz()` is executed before `foo()` so we need to use 'f' as its best
 | 
| +  /// position under the restriction.
 | 
| +  ///
 | 
| +  final int leftToRightOffset;
 | 
| +
 | 
| +  Offset(this.statementOffset, this.leftToRightOffset, this.subexpressionOffset);
 | 
| +
 | 
| +  String toString() {
 | 
| +    return 'Offset[statementOffset=$statementOffset,'
 | 
| +        'leftToRightOffset=$leftToRightOffset,'
 | 
| +        'subexpressionOffset=$subexpressionOffset]';
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +enum BranchKind {
 | 
| +  CONDITION,
 | 
| +  LOOP,
 | 
| +  CATCH,
 | 
| +  FINALLY,
 | 
| +  CASE,
 | 
| +}
 | 
| +
 | 
| +enum StepKind {
 | 
| +  FUN,
 | 
| +  CALL,
 | 
| +  NEW,
 | 
| +  RETURN,
 | 
| +  BREAK,
 | 
| +  CONTINUE,
 | 
| +  THROW,
 | 
| +  EXPRESSION_STATEMENT,
 | 
| +  IF_CONDITION,
 | 
| +  FOR_INITIALIZER,
 | 
| +  FOR_CONDITION,
 | 
| +  FOR_UPDATE,
 | 
| +  WHILE_CONDITION,
 | 
| +  DO_CONDITION,
 | 
| +  SWITCH_EXPRESSION,
 | 
| +}
 | 
| +
 | 
| +/// Listener for the [JavaScriptTracer].
 | 
| +abstract class TraceListener {
 | 
| +  /// Called before [root] node is procesed by the [JavaScriptTracer].
 | 
| +  void onStart(js.Node root) {}
 | 
| +
 | 
| +  /// Called after [root] node has been procesed by the [JavaScriptTracer].
 | 
| +  void onEnd(js.Node root) {}
 | 
| +
 | 
| +  /// Called when a branch of the given [kind] is started. [value] is provided
 | 
| +  /// to distinguish true/false branches of [BranchKind.CONDITION] and cases of
 | 
| +  /// [Branch.CASE].
 | 
| +  void pushBranch(BranchKind kind, [value]) {}
 | 
| +
 | 
| +  /// Called when the current branch ends.
 | 
| +  void popBranch() {}
 | 
| +
 | 
| +  /// Called when [node] defines a step of the given [kind] at the given
 | 
| +  /// [offset] when the generated JavaScript code.
 | 
| +  void onStep(js.Node node, Offset offset, StepKind kind) {}
 | 
| +}
 | 
| +
 | 
| +/// Visitor that computes the [js.Node]s the are part of the JavaScript
 | 
| +/// steppable execution and thus needs source mapping locations.
 | 
| +class JavaScriptTracer extends js.BaseVisitor  {
 | 
| +  final CodePositionMap codePositions;
 | 
| +  final List<TraceListener> listeners;
 | 
| +
 | 
| +  /// The steps added by subexpressions.
 | 
| +  List steps = [];
 | 
| +
 | 
| +  /// The offset of the current statement.
 | 
| +  int statementOffset;
 | 
| +
 | 
| +  /// The current offset in left-to-right progression.
 | 
| +  int leftToRightOffset;
 | 
| +
 | 
| +  /// The offset of the surrounding statement, used for the first subexpression.
 | 
| +  int offsetPosition;
 | 
| +
 | 
| +  bool active;
 | 
| +
 | 
| +  JavaScriptTracer(this.codePositions,
 | 
| +           this.listeners,
 | 
| +           {this.active: false});
 | 
| +
 | 
| +  void notifyStart(js.Node node) {
 | 
| +    listeners.forEach((listener) => listener.onStart(node));
 | 
| +  }
 | 
| +
 | 
| +  void notifyEnd(js.Node node) {
 | 
| +    listeners.forEach((listener) => listener.onEnd(node));
 | 
| +  }
 | 
| +
 | 
| +  void notifyPushBranch(BranchKind kind, [value]) {
 | 
| +    if (active) {
 | 
| +      listeners.forEach((listener) => listener.pushBranch(kind, value));
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  void notifyPopBranch() {
 | 
| +    if (active) {
 | 
| +      listeners.forEach((listener) => listener.popBranch());
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  void notifyStep(js.Node node, Offset offset, StepKind kind) {
 | 
| +    if (active) {
 | 
| +      listeners.forEach((listener) => listener.onStep(node, offset, kind));
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  void apply(js.Node node) {
 | 
| +    notifyStart(node);
 | 
| +    node.accept(this);
 | 
| +    notifyEnd(node);
 | 
| +  }
 | 
|  
 | 
|    @override
 | 
|    visitNode(js.Node node) {
 | 
| -    SourceInformation sourceInformation = node.sourceInformation;
 | 
| -    if (sourceInformation != null) {
 | 
| -      /// Associates the left-most position of the JS code with the left-most
 | 
| -      /// position of the Dart code.
 | 
| -      apply(node,
 | 
| -          node, CodePositionKind.START,
 | 
| -          sourceInformation, SourcePositionKind.START);
 | 
| +    node.visitChildren(this);
 | 
| +  }
 | 
| +
 | 
| +  visit(js.Node node, [BranchKind branch, value]) {
 | 
| +    if (node != null) {
 | 
| +      if (branch != null) {
 | 
| +        notifyPushBranch(branch, value);
 | 
| +        node.accept(this);
 | 
| +        notifyPopBranch();
 | 
| +      } else {
 | 
| +        node.accept(this);
 | 
| +      }
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  visitList(List<js.Node> nodeList) {
 | 
| +    if (nodeList != null) {
 | 
| +      for (js.Node node in nodeList) {
 | 
| +        visit(node);
 | 
| +      }
 | 
|      }
 | 
| -    visitChildren(node);
 | 
|    }
 | 
|  
 | 
|    @override
 | 
|    visitFun(js.Fun node) {
 | 
| -    SourceInformation sourceInformation = node.sourceInformation;
 | 
| -    if (sourceInformation != null) {
 | 
| -      /// Associates the end brace of the JavaScript function with the end brace
 | 
| -      /// of the Dart function (or the `;` in case of arrow notation).
 | 
| -      apply(node,
 | 
| -          node, CodePositionKind.CLOSING,
 | 
| -          sourceInformation, SourcePositionKind.CLOSING);
 | 
| +    bool activeBefore = active;
 | 
| +    if (!active) {
 | 
| +      active = node.sourceInformation != null;
 | 
| +    }
 | 
| +    visit(node.body);
 | 
| +    leftToRightOffset = statementOffset =
 | 
| +        getSyntaxOffset(node, kind: CodePositionKind.CLOSING);
 | 
| +    Offset offset = getOffsetForNode(node, statementOffset);
 | 
| +    notifyStep(node, offset, StepKind.FUN);
 | 
| +    active = activeBefore;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitBlock(js.Block node) {
 | 
| +    for (js.Statement statement in node.statements) {
 | 
| +      visit(statement);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  int getSyntaxOffset(js.Node node,
 | 
| +                      {CodePositionKind kind: CodePositionKind.START}) {
 | 
| +    CodePosition codePosition = codePositions[node];
 | 
| +    if (codePosition != null) {
 | 
| +      return codePosition.getPosition(kind);
 | 
|      }
 | 
| +    return null;
 | 
| +  }
 | 
|  
 | 
| -    visitChildren(node);
 | 
| +  visitSubexpression(js.Node parent,
 | 
| +                     js.Expression child,
 | 
| +                     int codeOffset,
 | 
| +                     StepKind kind) {
 | 
| +    var oldSteps = steps;
 | 
| +    steps = [];
 | 
| +    offsetPosition = codeOffset;
 | 
| +    visit(child);
 | 
| +    if (steps.isEmpty) {
 | 
| +      notifyStep(parent,
 | 
| +          getOffsetForNode(parent, offsetPosition),
 | 
| +          kind);
 | 
| +    }
 | 
| +    steps = oldSteps;
 | 
|    }
 | 
|  
 | 
|    @override
 | 
|    visitExpressionStatement(js.ExpressionStatement node) {
 | 
| -    visitChildren(node);
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visitSubexpression(
 | 
| +        node, node.expression, statementOffset,
 | 
| +        StepKind.EXPRESSION_STATEMENT);
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
|    }
 | 
|  
 | 
|    @override
 | 
| -  visitBinary(js.Binary node) {
 | 
| -    visitChildren(node);
 | 
| +  visitEmptyStatement(js.EmptyStatement node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitCall(js.Call node) {
 | 
| +    visit(node.target);
 | 
| +    int oldPosition = offsetPosition;
 | 
| +    offsetPosition = null;
 | 
| +    visitList(node.arguments);
 | 
| +    offsetPosition = oldPosition;
 | 
| +    CallPosition callPosition =
 | 
| +        CallPosition.getSemanticPositionForCall(node);
 | 
| +    js.Node positionNode = callPosition.node;
 | 
| +    int callOffset = getSyntaxOffset(
 | 
| +        positionNode, kind: callPosition.codePositionKind);
 | 
| +    if (offsetPosition == null) {
 | 
| +      offsetPosition = callOffset;
 | 
| +    }
 | 
| +    Offset offset = getOffsetForNode(positionNode, offsetPosition);
 | 
| +    notifyStep(node, offset, StepKind.CALL);
 | 
| +    steps.add(node);
 | 
| +    offsetPosition = null;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitNew(js.New node) {
 | 
| +    visit(node.target);
 | 
| +    visitList(node.arguments);
 | 
| +    notifyStep(
 | 
| +        node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.NEW);
 | 
| +    steps.add(node);
 | 
| +    offsetPosition = null;
 | 
|    }
 | 
|  
 | 
|    @override
 | 
|    visitAccess(js.PropertyAccess node) {
 | 
| -    visitChildren(node);
 | 
| +    visit(node.receiver);
 | 
| +    visit(node.selector);
 | 
|    }
 | 
|  
 | 
|    @override
 | 
| -  visitCall(js.Call node) {
 | 
| -    SourceInformation sourceInformation = node.sourceInformation;
 | 
| -    if (sourceInformation != null) {
 | 
| -      if (node.target is js.PropertyAccess) {
 | 
| -        js.PropertyAccess access = node.target;
 | 
| -        js.Node target = access;
 | 
| -        bool pureAccess = false;
 | 
| -        while (target is js.PropertyAccess) {
 | 
| -          js.PropertyAccess targetAccess = target;
 | 
| -          if (targetAccess.receiver is js.VariableUse ||
 | 
| -              targetAccess.receiver is js.This) {
 | 
| -            pureAccess = true;
 | 
| -            break;
 | 
| -          } else {
 | 
| -            target = targetAccess.receiver;
 | 
| -          }
 | 
| -        }
 | 
| -        if (pureAccess) {
 | 
| -          // a.m()   this.m()  a.b.c.d.m()
 | 
| -          // ^       ^         ^
 | 
| -          apply(
 | 
| -              node,
 | 
| -              node,
 | 
| -              CodePositionKind.START,
 | 
| -              sourceInformation,
 | 
| -              SourcePositionKind.START);
 | 
| -        } else {
 | 
| -          // *.m()  *.a.b.c.d.m()
 | 
| -          //   ^              ^
 | 
| -          apply(
 | 
| -              node,
 | 
| -              access.selector,
 | 
| -              CodePositionKind.START,
 | 
| -              sourceInformation,
 | 
| -              SourcePositionKind.CLOSING);
 | 
| -        }
 | 
| -      } else if (node.target is js.VariableUse) {
 | 
| -        // m()
 | 
| -        // ^
 | 
| -        apply(
 | 
| -            node,
 | 
| -            node,
 | 
| -            CodePositionKind.START,
 | 
| -            sourceInformation,
 | 
| -            SourcePositionKind.START);
 | 
| -      } else if (node.target is js.Fun || node.target is js.New) {
 | 
| -        // function(){}()  new Function("...")()
 | 
| -        //             ^                      ^
 | 
| -        apply(
 | 
| -            node,
 | 
| -            node.target,
 | 
| -            CodePositionKind.END,
 | 
| -            sourceInformation,
 | 
| -            SourcePositionKind.CLOSING);
 | 
| +  visitVariableUse(js.VariableUse node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitLiteralBool(js.LiteralBool node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitLiteralString(js.LiteralString node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitLiteralNumber(js.LiteralNumber node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitLiteralNull(js.LiteralNull node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitName(js.Name node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitVariableDeclarationList(js.VariableDeclarationList node) {
 | 
| +    visitList(node.declarations);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitVariableDeclaration(js.VariableDeclaration node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitVariableInitialization(js.VariableInitialization node) {
 | 
| +    visit(node.leftHandSide);
 | 
| +    visit(node.value);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitAssignment(js.Assignment node) {
 | 
| +    visit(node.leftHandSide);
 | 
| +    visit(node.value);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitIf(js.If node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visitSubexpression(node, node.condition, statementOffset,
 | 
| +        StepKind.IF_CONDITION);
 | 
| +    statementOffset = null;
 | 
| +    visit(node.then, BranchKind.CONDITION, true);
 | 
| +    visit(node.otherwise, BranchKind.CONDITION, false);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitFor(js.For node) {
 | 
| +    int offset = statementOffset = getSyntaxOffset(node);
 | 
| +    statementOffset = offset;
 | 
| +    leftToRightOffset = null;
 | 
| +    if (node.init != null) {
 | 
| +      visitSubexpression(node, node.init, getSyntaxOffset(node),
 | 
| +          StepKind.FOR_INITIALIZER);
 | 
| +    }
 | 
| +
 | 
| +    if (node.condition != null) {
 | 
| +      visitSubexpression(node, node.condition, getSyntaxOffset(node.condition),
 | 
| +          StepKind.FOR_CONDITION);
 | 
| +    }
 | 
| +
 | 
| +    notifyPushBranch(BranchKind.LOOP);
 | 
| +    visit(node.body);
 | 
| +
 | 
| +    statementOffset = offset;
 | 
| +    if (node.update != null) {
 | 
| +      visitSubexpression(node, node.update, getSyntaxOffset(node.update),
 | 
| +          StepKind.FOR_UPDATE);
 | 
| +    }
 | 
| +
 | 
| +    notifyPopBranch();
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitWhile(js.While node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    if (node.condition != null) {
 | 
| +      visitSubexpression(node, node.condition, getSyntaxOffset(node.condition),
 | 
| +          StepKind.WHILE_CONDITION);
 | 
| +    }
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +
 | 
| +    visit(node.body, BranchKind.LOOP);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitDo(js.Do node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visit(node.body);
 | 
| +    if (node.condition != null) {
 | 
| +      visitSubexpression(node, node.condition, getSyntaxOffset(node.condition),
 | 
| +          StepKind.DO_CONDITION);
 | 
| +    }
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitBinary(js.Binary node) {
 | 
| +    visit(node.left);
 | 
| +    visit(node.right);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitThis(js.This node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitReturn(js.Return node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visit(node.value);
 | 
| +    notifyStep(
 | 
| +        node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.RETURN);
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitThrow(js.Throw node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visit(node.expression);
 | 
| +    notifyStep(
 | 
| +        node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.THROW);
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitContinue(js.Continue node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    notifyStep(
 | 
| +        node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.CONTINUE);
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitBreak(js.Break node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    notifyStep(
 | 
| +        node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.BREAK);
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitTry(js.Try node) {
 | 
| +    visit(node.body);
 | 
| +    visit(node.catchPart, BranchKind.CATCH);
 | 
| +    visit(node.finallyPart, BranchKind.FINALLY);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitCatch(js.Catch node) {
 | 
| +    visit(node.body);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitConditional(js.Conditional node) {
 | 
| +    visit(node.condition);
 | 
| +    visit(node.then, BranchKind.CONDITION, true);
 | 
| +    visit(node.otherwise, BranchKind.CONDITION, false);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitPrefix(js.Prefix node) {
 | 
| +    visit(node.argument);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitPostfix(js.Postfix node) {
 | 
| +    visit(node.argument);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitObjectInitializer(js.ObjectInitializer node) {
 | 
| +    visitList(node.properties);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitProperty(js.Property node) {
 | 
| +    visit(node.name);
 | 
| +    visit(node.value);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitRegExpLiteral(js.RegExpLiteral node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitSwitch(js.Switch node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visitSubexpression(node, node.key, getSyntaxOffset(node),
 | 
| +        StepKind.SWITCH_EXPRESSION);
 | 
| +    statementOffset = null;
 | 
| +    leftToRightOffset = null;
 | 
| +    for (int i = 0; i < node.cases.length; i++) {
 | 
| +      visit(node.cases[i], BranchKind.CASE, i);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitCase(js.Case node) {
 | 
| +    visit(node.expression);
 | 
| +    visit(node.body);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitDefault(js.Default node) {
 | 
| +    visit(node.body);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitArrayInitializer(js.ArrayInitializer node) {
 | 
| +    visitList(node.elements);
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  visitArrayHole(js.ArrayHole node) {}
 | 
| +
 | 
| +  @override
 | 
| +  visitLabeledStatement(js.LabeledStatement node) {
 | 
| +    statementOffset = getSyntaxOffset(node);
 | 
| +    visit(node.body);
 | 
| +    statementOffset = null;
 | 
| +  }
 | 
| +
 | 
| +  Offset getOffsetForNode(js.Node node, int codeOffset) {
 | 
| +    if (codeOffset == null) {
 | 
| +      CodePosition codePosition = codePositions[node];
 | 
| +      if (codePosition != null) {
 | 
| +        codeOffset = codePosition.startPosition;
 | 
| +      }
 | 
| +    }
 | 
| +    if (leftToRightOffset != null && leftToRightOffset < codeOffset) {
 | 
| +      leftToRightOffset = codeOffset;
 | 
| +    }
 | 
| +    if (leftToRightOffset == null) {
 | 
| +      leftToRightOffset = statementOffset;
 | 
| +    }
 | 
| +    return new Offset(statementOffset, leftToRightOffset, codeOffset);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +
 | 
| +class Coverage {
 | 
| +  Set<js.Node> _nodesWithInfo = new Set<js.Node>();
 | 
| +  int _nodesWithInfoCount = 0;
 | 
| +  Set<js.Node> _nodesWithoutInfo = new Set<js.Node>();
 | 
| +  int _nodesWithoutInfoCount = 0;
 | 
| +  Map<Type, int> _nodesWithoutInfoCountByType = <Type, int>{};
 | 
| +  Set<js.Node> _nodesWithoutOffset = new Set<js.Node>();
 | 
| +  int _nodesWithoutOffsetCount = 0;
 | 
| +
 | 
| +  void registerNodeWithInfo(js.Node node) {
 | 
| +    _nodesWithInfo.add(node);
 | 
| +  }
 | 
| +
 | 
| +  void registerNodeWithoutInfo(js.Node node) {
 | 
| +    _nodesWithoutInfo.add(node);
 | 
| +  }
 | 
| +
 | 
| +  void registerNodesWithoutOffset(js.Node node) {
 | 
| +    _nodesWithoutOffset.add(node);
 | 
| +  }
 | 
| +
 | 
| +  void collapse() {
 | 
| +    _nodesWithInfoCount += _nodesWithInfo.length;
 | 
| +    _nodesWithInfo.clear();
 | 
| +    _nodesWithoutOffsetCount += _nodesWithoutOffset.length;
 | 
| +    _nodesWithoutOffset.clear();
 | 
| +
 | 
| +    _nodesWithoutInfoCount += _nodesWithoutInfo.length;
 | 
| +    for (js.Node node in _nodesWithoutInfo) {
 | 
| +      if (node is js.ExpressionStatement) {
 | 
| +        _nodesWithoutInfoCountByType.putIfAbsent(
 | 
| +            node.expression.runtimeType, () => 0);
 | 
| +        _nodesWithoutInfoCountByType[node.expression.runtimeType]++;
 | 
|        } else {
 | 
| -        assert(invariant(NO_LOCATION_SPANNABLE, false,
 | 
| -            message: "Unexpected property access ${nodeToString(node)}:\n"
 | 
| -                     "${DebugPrinter.prettyPrint(node)}"));
 | 
| -        // Don't know....
 | 
| -        apply(
 | 
| -            node,
 | 
| -            node,
 | 
| -            CodePositionKind.START,
 | 
| -            sourceInformation,
 | 
| -            SourcePositionKind.START);
 | 
| +        _nodesWithoutInfoCountByType.putIfAbsent(
 | 
| +            node.runtimeType, () => 0);
 | 
| +        _nodesWithoutInfoCountByType[node.runtimeType]++;
 | 
|        }
 | 
|      }
 | 
| -    visitChildren(node);
 | 
| +    _nodesWithoutInfo.clear();
 | 
|    }
 | 
|  
 | 
| +  String getCoverageReport() {
 | 
| +    collapse();
 | 
| +    StringBuffer sb = new StringBuffer();
 | 
| +    int total = _nodesWithInfoCount + _nodesWithoutInfoCount;
 | 
| +    if (total > 0) {
 | 
| +      sb.write(_nodesWithoutInfoCount);
 | 
| +      sb.write('/');
 | 
| +      sb.write(total);
 | 
| +      sb.write(' (');
 | 
| +      sb.write((100.0 * _nodesWithInfoCount / total).toStringAsFixed(2));
 | 
| +      sb.write('%) nodes with info.');
 | 
| +    } else {
 | 
| +      sb.write('No nodes.');
 | 
| +    }
 | 
| +    if (_nodesWithoutOffsetCount > 0) {
 | 
| +      sb.write(' ');
 | 
| +      sb.write(_nodesWithoutOffsetCount);
 | 
| +      sb.write(' node');
 | 
| +      if (_nodesWithoutOffsetCount > 1) {
 | 
| +        sb.write('s');
 | 
| +      }
 | 
| +      sb.write(' without offset.');
 | 
| +    }
 | 
| +    if (_nodesWithoutInfoCount > 0) {
 | 
| +      sb.write('\nNodes without info (');
 | 
| +      sb.write(_nodesWithoutInfoCount);
 | 
| +      sb.write(') by runtime type:');
 | 
| +      _nodesWithoutInfoCountByType.forEach((Type type, int count) {
 | 
| +        sb.write('\n ');
 | 
| +        sb.write(count);
 | 
| +        sb.write(' ');
 | 
| +        sb.write(type);
 | 
| +        sb.write(' node');
 | 
| +        if (count > 1) {
 | 
| +          sb.write('s');
 | 
| +        }
 | 
| +      });
 | 
| +      sb.write('\n');
 | 
| +    }
 | 
| +    return sb.toString();
 | 
| +  }
 | 
| +
 | 
| +  String toString() => getCoverageReport();
 | 
| +}
 | 
| +
 | 
| +/// [TraceListener] that registers [onStep] callbacks with [coverage].
 | 
| +class CoverageListener extends TraceListener {
 | 
| +  final Coverage coverage;
 | 
| +
 | 
| +  CoverageListener(this.coverage);
 | 
| +
 | 
|    @override
 | 
| -  void onPositions(js.Node node,
 | 
| -                   int startPosition,
 | 
| -                   int endPosition,
 | 
| -                   int closingPosition) {
 | 
| -    codePositions.registerPositions(
 | 
| -        node, startPosition, endPosition, closingPosition);
 | 
| +  void onStep(js.Node node, Offset offset, StepKind kind) {
 | 
| +    SourceInformation sourceInformation = node.sourceInformation;
 | 
| +    if (sourceInformation != null) {
 | 
| +      coverage.registerNodeWithInfo(node);
 | 
| +    } else {
 | 
| +      coverage.registerNodeWithoutInfo(node);
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  @override
 | 
| +  void onEnd(js.Node node) {
 | 
| +    coverage.collapse();
 | 
|    }
 | 
|  }
 | 
| +
 | 
| +/// [CodePositionMap] that registers calls with [Coverage].
 | 
| +class CodePositionCoverage implements CodePositionMap {
 | 
| +  final CodePositionMap codePositions;
 | 
| +  final Coverage coverage;
 | 
| +
 | 
| +  CodePositionCoverage(this.codePositions, this.coverage);
 | 
| +
 | 
| +  @override
 | 
| +  CodePosition operator [](js.Node node) {
 | 
| +    CodePosition codePosition = codePositions[node];
 | 
| +    if (codePosition == null) {
 | 
| +      coverage.registerNodesWithoutOffset(node);
 | 
| +   }
 | 
| +    return codePosition;
 | 
| +  }
 | 
| +}
 | 
| 
 |