1 | import libCoverage from "istanbul-lib-coverage";
|
2 | import { Position, SourceLocation } from "./index";
|
3 |
|
4 | export class CoverageMapBuilder {
|
5 | private readonly files: Map<string, FileCoverageBuilder>;
|
6 |
|
7 | constructor() {
|
8 | this.files = new Map();
|
9 | }
|
10 |
|
11 | addStatement(url: string, loc: SourceLocation, count: number): void {
|
12 | this.getOrCreateFileCoverageBuilder(url).addStatement(loc, count);
|
13 | }
|
14 |
|
15 | addFunction(url: string, decl: SourceLocation, loc: SourceLocation, count: number, name: string): void {
|
16 | this.getOrCreateFileCoverageBuilder(url).addFunction(decl, loc, count, name);
|
17 | }
|
18 |
|
19 | addBranch(url: string, mainLoc: SourceLocation, arms: Iterable<[SourceLocation, number]>, type: string): void {
|
20 | this.getOrCreateFileCoverageBuilder(url).addBranch(mainLoc, arms, type);
|
21 | }
|
22 |
|
23 | build(): libCoverage.CoverageMapData {
|
24 | const data: libCoverage.CoverageMapData = Object.create(null);
|
25 | for (const [url, file] of this.files) {
|
26 | data[url] = libCoverage.createFileCoverage(file.build(url));
|
27 | }
|
28 | return data;
|
29 | }
|
30 |
|
31 | private getOrCreateFileCoverageBuilder(url: string): FileCoverageBuilder {
|
32 | let builder: FileCoverageBuilder | undefined = this.files.get(url);
|
33 | if (builder === undefined) {
|
34 | builder = new FileCoverageBuilder();
|
35 | this.files.set(url, builder);
|
36 | }
|
37 | return builder;
|
38 | }
|
39 | }
|
40 |
|
41 | interface Branch {
|
42 | readonly loc: SourceLocation;
|
43 | readonly arms: ReadonlyArray<BranchArm>;
|
44 | readonly type: string;
|
45 | }
|
46 |
|
47 | interface BranchArm {
|
48 | readonly loc: SourceLocation;
|
49 | count: number;
|
50 | }
|
51 |
|
52 | interface Function {
|
53 | readonly decl: SourceLocation;
|
54 | readonly loc: SourceLocation;
|
55 | name?: string;
|
56 | count: number;
|
57 | }
|
58 |
|
59 | interface Statement {
|
60 | readonly loc: SourceLocation;
|
61 | count: number;
|
62 | }
|
63 |
|
64 | export class FileCoverageBuilder {
|
65 | private readonly branches: Map<string, Branch>;
|
66 | private readonly functions: Map<string, Function>;
|
67 | private readonly statements: Map<string, Statement>;
|
68 |
|
69 | constructor() {
|
70 | this.branches = new Map();
|
71 | this.functions = new Map();
|
72 | this.statements = new Map();
|
73 | }
|
74 |
|
75 | addStatement(loc: SourceLocation, count: number): void {
|
76 | const hash: string = hashSourceLocation(loc);
|
77 | let statement: Statement | undefined = this.statements.get(hash);
|
78 | if (statement === undefined) {
|
79 | statement = {loc, count: 0};
|
80 | this.statements.set(hash, statement);
|
81 | }
|
82 | statement.count += count;
|
83 | }
|
84 |
|
85 | addFunction(decl: SourceLocation, loc: SourceLocation, count: number, name?: string): void {
|
86 | const hash: string = hashSourceLocation(decl);
|
87 | let func: Function | undefined = this.functions.get(hash);
|
88 | if (func === undefined) {
|
89 | func = {decl, loc, name, count: 0};
|
90 | this.functions.set(hash, func);
|
91 | }
|
92 | func.count += count;
|
93 | if (func.name === undefined) {
|
94 | func.name = name;
|
95 | }
|
96 | }
|
97 |
|
98 | addBranch(mainLoc: SourceLocation, arms: Iterable<[SourceLocation, number]>, type: string): void {
|
99 | const armHashes: string[] = [];
|
100 | for (const [loc, _] of arms) {
|
101 | armHashes.push(hashSourceLocation(loc));
|
102 | }
|
103 | const hash: string = `${hashSourceLocation(mainLoc)}(${armHashes.join(",")})`;
|
104 | let branch: Branch | undefined = this.branches.get(hash);
|
105 | if (branch === undefined) {
|
106 | const newArms: BranchArm[] = [];
|
107 | for (const [loc, _] of arms) {
|
108 | newArms.push({loc, count: 0});
|
109 | }
|
110 | branch = {type, loc: mainLoc, arms: newArms};
|
111 | this.branches.set(hash, branch);
|
112 | }
|
113 | let i: number = 0;
|
114 | for (const [_, count] of arms) {
|
115 | branch.arms[i].count += count;
|
116 | i++;
|
117 | }
|
118 | }
|
119 |
|
120 | build(url: string): libCoverage.FileCoverageData {
|
121 | const statementMap: Record<string, any> = Object.create(null);
|
122 | const s: Record<string, number> = Object.create(null);
|
123 | let sid: number = 0;
|
124 | for (const {loc, count} of this.statements.values()) {
|
125 | const key: string = `s${sid}`;
|
126 | sid++;
|
127 | statementMap[key] = loc;
|
128 | s[key] = count;
|
129 | }
|
130 |
|
131 | const fnMap: Record<string, any> = Object.create(null);
|
132 | const f: Record<string, number> = Object.create(null);
|
133 | let fid: number = 0;
|
134 | for (const {decl, loc, name, count} of this.functions.values()) {
|
135 | const key: string = `f${fid}`;
|
136 | fid++;
|
137 | fnMap[key] = {decl, loc, name: name === undefined ? `unknown_${key}` : name};
|
138 | f[key] = count;
|
139 | }
|
140 |
|
141 | const branchMap: Record<string, any> = Object.create(null);
|
142 | const b: Record<string, number[]> = Object.create(null);
|
143 |
|
144 | return {path: url, statementMap, s, fnMap, f, branchMap, b};
|
145 | }
|
146 | }
|
147 |
|
148 | export function hashSourceLocation(loc: SourceLocation): string {
|
149 | return `${hashPosition(loc.start)}-${hashPosition(loc.end)}`;
|
150 | }
|
151 |
|
152 | export function hashPosition(pos: Position): string {
|
153 | return `${pos.line}:${pos.column}`;
|
154 | }
|