Index: observatory_pub_packages/analyzer/src/services/runtime/coverage/models.dart |
=================================================================== |
--- observatory_pub_packages/analyzer/src/services/runtime/coverage/models.dart (revision 0) |
+++ observatory_pub_packages/analyzer/src/services/runtime/coverage/models.dart (working copy) |
@@ -0,0 +1,168 @@ |
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/// A library with code coverage models. |
+library runtime.coverage.model; |
+ |
+import 'dart:collection' show SplayTreeMap; |
+ |
+import 'package:analyzer/src/generated/source.dart' show Source, SourceRange; |
+import 'package:analyzer/src/generated/ast.dart' show AstNode; |
+ |
+import 'utils.dart'; |
+ |
+ |
+/// Contains information about the application. |
+class AppInfo { |
+ final nodeStack = new List<NodeInfo>(); |
+ final units = new List<UnitInfo>(); |
+ final pathToFile = new Map<String, UnitInfo>(); |
+ NodeInfo currentNode; |
+ int nextId = 0; |
+ |
+ void enterUnit(String path, String content) { |
+ var unit = new UnitInfo(this, path, content); |
+ units.add(unit); |
+ currentNode = unit; |
+ } |
+ |
+ void enter(String kind, String name) { |
+ nodeStack.add(currentNode); |
+ currentNode = new NodeInfo(this, currentNode, kind, name); |
+ } |
+ |
+ void leave() { |
+ currentNode = nodeStack.removeLast(); |
+ } |
+ |
+ int addNode(AstNode node) { |
+ return currentNode.addNode(node); |
+ } |
+ |
+ void write(StringSink sink, Set<int> executedIds) { |
+ sink.writeln('{'); |
+ units.fold(null, (prev, unit) { |
+ if (prev != null) sink.writeln(','); |
+ return unit..write(sink, executedIds, ' '); |
+ }); |
+ sink.writeln(); |
+ sink.writeln('}'); |
+ } |
+} |
+ |
+/// Information about some node - unit, class, method, function. |
+class NodeInfo { |
+ final AppInfo appInfo; |
+ final NodeInfo parent; |
+ final String kind; |
+ final String name; |
+ final idToRange = new SplayTreeMap<int, SourceRange>(); |
+ final children = <NodeInfo>[]; |
+ |
+ NodeInfo(this.appInfo, this.parent, this.kind, this.name) { |
+ if (parent != null) { |
+ parent.children.add(this); |
+ } |
+ } |
+ |
+ int addNode(AstNode node) { |
+ var id = appInfo.nextId++; |
+ var range = new SourceRange(node.offset, node.length); |
+ idToRange[id] = range; |
+ return id; |
+ } |
+ |
+ void write(StringSink sink, Set<int> executedIds, String prefix) { |
+ sink.writeln('$prefix"$name": {'); |
+ // Kind. |
+ sink.writeln('$prefix "kind": "$kind",'); |
+ // Print children. |
+ if (children.isNotEmpty) { |
+ sink.writeln('$prefix "children": {'); |
+ children.fold(null, (prev, child) { |
+ if (prev != null) sink.writeln(','); |
+ return child..write(sink, executedIds, '$prefix '); |
+ }); |
+ sink.writeln(); |
+ sink.writeln('$prefix }'); |
+ } |
+ // Print source and line ranges. |
+ if (children.isEmpty) { |
+ sink.write('${prefix} "ranges": ['); |
+ var rangePrinter = new RangePrinter(unit, sink, executedIds); |
+ idToRange.forEach(rangePrinter.handle); |
+ rangePrinter.printRange(); |
+ sink.writeln(']'); |
+ } |
+ // Close this node. |
+ sink.write('$prefix}'); |
+ } |
+ |
+ UnitInfo get unit => parent.unit; |
+} |
+ |
+/// Helper for printing merged source/line intervals. |
+class RangePrinter { |
+ final UnitInfo unit; |
+ final StringSink sink; |
+ final Set<int> executedIds; |
+ |
+ bool first = true; |
+ int startId = -1; |
+ int startOffset = -1; |
+ int endId = -1; |
+ int endOffset = -1; |
+ |
+ RangePrinter(this.unit, this.sink, this.executedIds); |
+ |
+ handle(int id, SourceRange range) { |
+ if (executedIds.contains(id)) { |
+ printRange(); |
+ } else { |
+ if (endId == id - 1) { |
+ endId = id; |
+ endOffset = range.end; |
+ } else { |
+ startId = id; |
+ endId = id; |
+ startOffset = range.offset; |
+ endOffset = range.end; |
+ } |
+ } |
+ } |
+ |
+ void printRange() { |
+ if (endId == -1) return; |
+ printSeparator(); |
+ var startLine = unit.getLine(startOffset); |
+ var endLine = unit.getLine(endOffset); |
+ sink.write('$startOffset,$endOffset,$startLine,$endLine'); |
+ startId = startOffset = startLine = -1; |
+ endId = endOffset = endLine = -1; |
+ } |
+ |
+ void printSeparator() { |
+ if (first) { |
+ first = false; |
+ } else { |
+ sink.write(', '); |
+ } |
+ } |
+} |
+ |
+/// Contains information about the single unit of the application. |
+class UnitInfo extends NodeInfo { |
+ List<int> lineOffsets; |
+ |
+ UnitInfo(AppInfo appInfo, String path, String content) |
+ : super(appInfo, null, 'unit', path) { |
+ lineOffsets = getLineOffsets(content); |
+ } |
+ |
+ UnitInfo get unit => this; |
+ |
+ int getLine(int offset) { |
+ return binarySearch(lineOffsets, (x) => x >= offset); |
+ } |
+} |