| // 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_experimental/src/generated/source.dart' show Source, SourceRange; |
| import 'package:analyzer_experimental/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); |
| } |
| } |