| 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;
|
| + }
|
| +}
|
|
|