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