Chromium Code Reviews| 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..14c8163c08b6af887c59940ca01aa1a33fb2c128 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'; |
| @@ -102,7 +104,9 @@ class PositionSourceInformation extends SourceInformation { |
| class PositionSourceInformationStrategy |
| implements JavaScriptSourceInformationStrategy { |
| - const PositionSourceInformationStrategy(); |
| + 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
|
| + |
| + PositionSourceInformationStrategy([this.coverage]); |
| @override |
| SourceInformationBuilder createBuilderForContext(AstElement element) { |
| @@ -111,10 +115,39 @@ class PositionSourceInformationStrategy |
| @override |
| SourceInformationProcessor createProcessor(SourceMapper mapper) { |
| - return new PositionSourceInformationProcessor(mapper); |
| + return new PositionSourceInformationProcessor(mapper, coverage); |
| + } |
| + |
| + void onComplete() { |
| + if (coverage != null) { |
| + coverage.collapse(); |
| + } |
| + } |
| + |
| + @override |
| + SourceInformation buildPreambleMarker() { |
| + return const PreambleMarker(); |
| } |
| } |
| +/// 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 = [["_
|
| +/// |
| +/// 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 PreambleMarker extends SourceInformation { |
| + const PreambleMarker(); |
| + |
| + @override |
| + String get shortText => ''; |
| + |
| + @override |
| + List<SourceLocation> get sourceLocations => const <SourceLocation>[]; |
| + |
| + @override |
| + SourceSpan get sourceSpan => new SourceSpan(null, null, null); |
| +} |
| + |
| /// [SourceInformationBuilder] that generates [PositionSourceInformation]. |
| class PositionSourceInformationBuilder implements SourceInformationBuilder { |
| final SourceFile sourceFile; |
| @@ -225,10 +258,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(); |
| @@ -261,198 +315,769 @@ enum CodePositionKind { |
| /// 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); |
| - CodePosition getCodePosition(js.Node node) { |
| - return codePositions[node]; |
| + @override |
| + void onStep(js.Node node, Offset offset, StepKind kind) { |
| + SourceInformation sourceInformation = node.sourceInformation; |
| + if (sourceInformation == null) return; |
| + |
| + SourcePositionKind sourcePositionKind = SourcePositionKind.START; |
| + switch (kind) { |
| + case StepKind.FUN: |
| + sourcePositionKind = SourcePositionKind.CLOSING; |
| + 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: |
|
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
|
| + break; |
| + } |
| + int codeLocation = offset.codeOffset; |
| + 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.
|
| + switch (sourcePositionKind) { |
| + case SourcePositionKind.START: |
| + sourceLocation = sourceInformation.startPosition; |
| + break; |
| + case SourcePositionKind.CLOSING: |
| + sourceLocation = sourceInformation.closingPosition; |
| + break; |
| + case SourcePositionKind.END: |
| + sourceLocation = sourceInformation.endPosition; |
| + break; |
| + } |
| + 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.CLOSING); |
| } |
| + } 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.CLOSING); |
| + } 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 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.
|
| + final int statementOffset; |
| + |
| + /// 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.
|
| + final int leftToRightOffset; |
| + |
| + /// 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.
|
| + final int codeOffset; |
| + |
| + Offset(this.statementOffset, this.leftToRightOffset, this.codeOffset); |
| + |
| + String toString() { |
| + return 'Offset[statementOffset=$statementOffset,' |
| + 'leftToRightOffset=$leftToRightOffset,' |
| + 'codeOffset=$codeOffset]'; |
| + } |
| +} |
| + |
| +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); |
| } |
| + } |
| - visitChildren(node); |
| + int getSyntaxOffset(js.Node node, |
| + {CodePositionKind kind: CodePositionKind.START}) { |
| + CodePosition codePosition = codePositions[node]; |
| + if (codePosition != null) { |
| + return codePosition.getPosition(kind); |
| + } |
| + return null; |
| + } |
| + |
| + 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; |
|
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
|
| + } |
| + |
| + @override |
| + visitNew(js.New node) { |
| + visit(node.target); |
| + visitList(node.arguments); |
| + notifyStep( |
| + 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.
|
| + 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; |
| + } |
| +} |