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..51c706160088d3b151802bc05596d63d87c468c0 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.codeOffset; |
| + 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 codeOffset; |
| + |
| + /// The `left-to-right` offset of the step. This is a restricted version of |
| + /// [subexpressionOffset] where offset by the execution order are always |
|
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
A few notes:
- [subexpressionOffset] => [codeOffs
Johnni Winther
2016/01/25 09:58:55
Done.
|
| + /// increasing within the same statement. |
| + /// |
| + /// 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()` be needs to use 'f' as its best |
|
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
nit: "be needs" => "so we need to"?
Johnni Winther
2016/01/25 09:58:55
Done.
|
| + /// position under the restriction. |
| + /// |
| + final int leftToRightOffset; |
|
Siggi Cherem (dart-lang)
2016/01/22 19:32:58
another idea for the name here: executionOrderOffs
Johnni Winther
2016/01/25 09:58:55
I find that equally confusing :(
|
| + |
| + 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); |
| + } |
| + } |
| + |
| + 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; |
| + } |
| +} |