1 | import libCoverage from "istanbul-lib-coverage";
|
2 | import sourceMap from "source-map";
|
3 | import url from "url";
|
4 | import { CoverageMapBuilder } from "./builder";
|
5 | import { SourceStore } from "./source-store";
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | export interface SourceLocationWithUrl extends SourceLocation {
|
11 | |
12 |
|
13 |
|
14 | readonly url: string;
|
15 | }
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | export interface SourceLocation {
|
21 | |
22 |
|
23 |
|
24 | readonly start: Position;
|
25 |
|
26 | |
27 |
|
28 |
|
29 | readonly end: Position;
|
30 | }
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | export interface Position {
|
36 | |
37 |
|
38 |
|
39 |
|
40 |
|
41 | readonly line: number;
|
42 |
|
43 | |
44 |
|
45 |
|
46 |
|
47 |
|
48 | readonly column: number;
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | export async function addFromGeneratedFileCov(
|
61 | covMapBuilder: CoverageMapBuilder,
|
62 | sourceStore: SourceStore,
|
63 | generatedFileCov: libCoverage.FileCoverageData,
|
64 | rawSourceMap: sourceMap.RawSourceMap,
|
65 | sourceMapUrl?: string,
|
66 | ): Promise<boolean> {
|
67 | return sourceMap.SourceMapConsumer.with(
|
68 | rawSourceMap,
|
69 | undefined,
|
70 |
|
71 | <(smc: any) => Promise<boolean>> (async (smc: sourceMap.BasicSourceMapConsumer): Promise<boolean> => {
|
72 | const didUpdateSources: boolean = addSourceTexts(sourceStore, smc, generatedFileCov.path);
|
73 | const didUpdateCovMap: boolean = addOriginalFileCovs(covMapBuilder, smc, generatedFileCov);
|
74 | return didUpdateSources || didUpdateCovMap;
|
75 | }),
|
76 | );
|
77 | }
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 | export function addSourceTexts(
|
88 | sourceStore: SourceStore,
|
89 | smc: sourceMap.BasicSourceMapConsumer,
|
90 | generatedUrl: string,
|
91 | ): boolean {
|
92 | let didUpdate: boolean = false;
|
93 | for (const [url, sourceText] of getSourceTexts(smc, generatedUrl)) {
|
94 | const curDidUpdate: boolean = sourceStore.set(url, sourceText);
|
95 | didUpdate = didUpdate || curDidUpdate;
|
96 | }
|
97 | return didUpdate;
|
98 | }
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | export function* getSourceTexts(
|
110 | smc: sourceMap.BasicSourceMapConsumer,
|
111 | generatedUrl: string,
|
112 | ): IterableIterator<[string, string]> {
|
113 | for (const source of smc.sources) {
|
114 | const sourceText: string | null = smc.sourceContentFor(source);
|
115 | if (sourceText === null) {
|
116 | continue;
|
117 | }
|
118 | const originalUrl: string = url.resolve(generatedUrl, source);
|
119 | yield [originalUrl, sourceText];
|
120 | }
|
121 | }
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 | export function addOriginalFileCovs(
|
132 | covMapBuilder: CoverageMapBuilder,
|
133 | smc: sourceMap.BasicSourceMapConsumer,
|
134 | generatedFileCov: libCoverage.FileCoverageData,
|
135 | ): boolean {
|
136 | let didUpdate: boolean = false;
|
137 | const generatedUrl: string = generatedFileCov.path;
|
138 |
|
139 | for (const [key, loc] of Object.entries(generatedFileCov.statementMap)) {
|
140 | const count: number = generatedFileCov.s[key];
|
141 | const originalLoc: SourceLocationWithUrl | undefined = tryGetOriginalLocation(smc, generatedUrl, loc);
|
142 | if (originalLoc === undefined) {
|
143 | continue;
|
144 | }
|
145 | covMapBuilder.addStatement(originalLoc.url, originalLoc, count);
|
146 | didUpdate = true;
|
147 | }
|
148 |
|
149 | for (const [key, funcMapping] of Object.entries(generatedFileCov.fnMap)) {
|
150 | const count: number = generatedFileCov.f[key];
|
151 | const originalDecl: SourceLocationWithUrl | undefined = tryGetOriginalLocation(smc, generatedUrl, funcMapping.decl);
|
152 | const originalLoc: SourceLocationWithUrl | undefined = tryGetOriginalLocation(smc, generatedUrl, funcMapping.loc);
|
153 | if (originalDecl === undefined || originalLoc === undefined || originalDecl.url !== originalLoc.url) {
|
154 | continue;
|
155 | }
|
156 | covMapBuilder.addFunction(originalDecl.url, originalDecl, originalLoc, count, funcMapping.name);
|
157 | didUpdate = true;
|
158 | }
|
159 |
|
160 | branches: for (const [key, branchMapping] of Object.entries(generatedFileCov.branchMap)) {
|
161 | const counts: ReadonlyArray<number> = generatedFileCov.b[key];
|
162 | const mainLoc: SourceLocation = branchMapping.loc;
|
163 | const originalMainLoc: SourceLocationWithUrl | undefined = tryGetOriginalLocation(smc, generatedUrl, mainLoc);
|
164 | if (originalMainLoc === undefined) {
|
165 | continue;
|
166 | }
|
167 | const arms: [SourceLocation, number][] = [];
|
168 | for (const [i, loc] of branchMapping.locations.entries()) {
|
169 | const count: number = counts[i];
|
170 | const originalLoc: SourceLocationWithUrl | undefined = tryGetOriginalLocation(smc, generatedUrl, loc);
|
171 | if (originalLoc === undefined || originalLoc.url !== originalMainLoc.url) {
|
172 | continue branches;
|
173 | }
|
174 | arms.push([originalLoc, count]);
|
175 | }
|
176 | covMapBuilder.addBranch(originalMainLoc.url, originalMainLoc, arms, branchMapping.type);
|
177 | didUpdate = true;
|
178 | }
|
179 |
|
180 | return didUpdate;
|
181 | }
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 | function tryGetOriginalLocation(
|
195 | smc: sourceMap.BasicSourceMapConsumer,
|
196 | generatedUrl: string,
|
197 | generatedLocation: SourceLocation,
|
198 | ): SourceLocationWithUrl | undefined {
|
199 |
|
200 | const oStart: sourceMap.NullableMappedPosition = smc.originalPositionFor({
|
201 | ...generatedLocation.start,
|
202 | bias: sourceMap.SourceMapConsumer.LEAST_UPPER_BOUND,
|
203 | });
|
204 |
|
205 | if (oStart.source === null || oStart.line === null || oStart.column === null) {
|
206 |
|
207 | return undefined;
|
208 | }
|
209 |
|
210 |
|
211 | let oEnd: sourceMap.NullableMappedPosition = smc.originalPositionFor({
|
212 | ...generatedLocation.end,
|
213 | bias: sourceMap.SourceMapConsumer.LEAST_UPPER_BOUND,
|
214 | });
|
215 |
|
216 | if (oEnd.source === oStart.source && oEnd.line === oStart.line && oEnd.column === oStart.column) {
|
217 |
|
218 | oEnd = smc.originalPositionFor({
|
219 | ...generatedLocation.end,
|
220 | bias: sourceMap.SourceMapConsumer.GREATEST_LOWER_BOUND,
|
221 | });
|
222 | }
|
223 |
|
224 | if (oEnd.source === null || oEnd.line === null || oEnd.column === null) {
|
225 |
|
226 | return undefined;
|
227 | }
|
228 |
|
229 | if (oEnd.source !== oStart.source) {
|
230 | return undefined;
|
231 | }
|
232 |
|
233 | const originalUrl: string = url.resolve(generatedUrl, oStart.source);
|
234 |
|
235 | return {
|
236 | url: originalUrl,
|
237 | start: {line: oStart.line, column: oStart.column},
|
238 | end: {line: oEnd.line, column: oEnd.column},
|
239 | };
|
240 | }
|