blob: 74fc7aa48923ff4f0af4301e764e6ac875d5acfd [file] [log] [blame]
// 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);
}
}