monocart-coverage-reports 2.13.0 → 2.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-Hans.md +1 -1
- package/lib/converter/collect-source-maps.js +117 -79
- package/lib/converter/converter.js +8 -2
- package/lib/converter/untested.js +0 -8
- package/lib/generate.js +7 -3
- package/lib/packages/monocart-coverage-assets.js +1 -1
- package/lib/packages/monocart-coverage-vendor.js +14 -14
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +525 -0
- package/package.json +28 -28
package/README.md
CHANGED
|
@@ -671,7 +671,7 @@ const coverageOptions = {
|
|
|
671
671
|
```
|
|
672
672
|
|
|
673
673
|
## Adding Empty Coverage for Untested Files
|
|
674
|
-
By default the untested files will not be included in the coverage report
|
|
674
|
+
By default the untested files will not be included in the coverage report. Use `all` to include them with 0% coverage. This also works when no coverage data has been collected: a V8 report is generated from the matching untested files.
|
|
675
675
|
```js
|
|
676
676
|
const coverageOptions = {
|
|
677
677
|
all: './src',
|
package/README.zh-Hans.md
CHANGED
|
@@ -674,7 +674,7 @@ const coverageOptions = {
|
|
|
674
674
|
```
|
|
675
675
|
|
|
676
676
|
## Adding Empty Coverage for Untested Files
|
|
677
|
-
|
|
677
|
+
默认,未测试的文件不会包含在覆盖率报告中。使用 `all` 选项可将它们以 0% 覆盖率加入报告。即使没有收集到任何覆盖率数据,也会为匹配的未测试文件生成 V8 报告。
|
|
678
678
|
```js
|
|
679
679
|
const coverageOptions = {
|
|
680
680
|
all: './src',
|
|
@@ -8,6 +8,72 @@ const { flattenSourceMaps } = require('./flatten-source-maps.js');
|
|
|
8
8
|
|
|
9
9
|
const Util = require('../utils/util.js');
|
|
10
10
|
|
|
11
|
+
const getSourceMapCandidates = (source) => {
|
|
12
|
+
if (!source.includes('sourceMappingURL=')) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Match directive-shaped text without parsing JS; valid maps in strings can be selected.
|
|
17
|
+
const candidates = [];
|
|
18
|
+
for (const match of source.matchAll(convertSourceMap.commentRegex)) {
|
|
19
|
+
candidates.push({
|
|
20
|
+
index: match.index,
|
|
21
|
+
comment: match[0],
|
|
22
|
+
inline: true
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
for (const match of source.matchAll(convertSourceMap.mapFileCommentRegex)) {
|
|
26
|
+
const filename = match[1] || match[2];
|
|
27
|
+
if (filename && !filename.startsWith('data:')) {
|
|
28
|
+
candidates.push({
|
|
29
|
+
index: match.index,
|
|
30
|
+
filename
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Like 2.13.0, try inline maps before external ones. Within each kind,
|
|
35
|
+
// start at the last directive and fall back when it is unusable.
|
|
36
|
+
return candidates.sort((a, b) => {
|
|
37
|
+
if (a.inline !== b.inline) {
|
|
38
|
+
return a.inline ? -1 : 1;
|
|
39
|
+
}
|
|
40
|
+
return b.index - a.index;
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Only check the fields required to resolve a map; decoding is deferred to
|
|
45
|
+
// the downstream converter. A map without sources cannot map to original code.
|
|
46
|
+
const isValidBasicMap = (data) => Array.isArray(data.sources)
|
|
47
|
+
&& data.sources.every((source) => typeof source === 'string')
|
|
48
|
+
&& typeof data.mappings === 'string'
|
|
49
|
+
&& (data.sources.length > 0 || data.mappings === '');
|
|
50
|
+
|
|
51
|
+
const isValidSection = (section) => section && section.offset
|
|
52
|
+
&& Number.isInteger(section.offset.line) && section.offset.line >= 0
|
|
53
|
+
&& Number.isInteger(section.offset.column) && section.offset.column >= 0
|
|
54
|
+
&& isValidSourceMap(section.map);
|
|
55
|
+
|
|
56
|
+
const isValidSourceMap = (data) => {
|
|
57
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (Array.isArray(data.sections)) {
|
|
61
|
+
return data.sections.every(isValidSection);
|
|
62
|
+
}
|
|
63
|
+
return isValidBasicMap(data);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// An empty map cannot map coverage to any original source. Try the next
|
|
67
|
+
// candidate instead, including when an indexed map has only empty sections.
|
|
68
|
+
const hasMappings = (data) => {
|
|
69
|
+
if (Array.isArray(data.sections)) {
|
|
70
|
+
return data.sections.some((section) => hasMappings(section.map));
|
|
71
|
+
}
|
|
72
|
+
return data.mappings.length > 0;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isUsableSourceMap = (data) => isValidSourceMap(data) && hasMappings(data);
|
|
76
|
+
|
|
11
77
|
const defaultSourceMapResolver = async (url = '') => {
|
|
12
78
|
|
|
13
79
|
if (url.startsWith('file:')) {
|
|
@@ -48,27 +114,9 @@ const loadSourceMap = async (url, options) => {
|
|
|
48
114
|
return defaultSourceMapResolver(url);
|
|
49
115
|
};
|
|
50
116
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!m) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const comment = m.pop();
|
|
59
|
-
const r = convertSourceMap.mapFileCommentRegex.exec(comment);
|
|
60
|
-
// for some odd reason //# .. captures in 1 and /* .. */ in 2
|
|
61
|
-
const filename = r[1] || r[2];
|
|
62
|
-
|
|
63
|
-
const urlObj = Util.resolveUrl(filename, url);
|
|
64
|
-
if (urlObj) {
|
|
65
|
-
return urlObj.toString();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const mapUrl = Util.resolveUrl(filename, pathToFileURL(url).toString());
|
|
69
|
-
if (mapUrl) {
|
|
70
|
-
return mapUrl.toString();
|
|
71
|
-
}
|
|
117
|
+
const resolveMapUrl = (filename, url) => {
|
|
118
|
+
const urlObj = Util.resolveUrl(filename, url) || Util.resolveUrl(filename, pathToFileURL(url).toString());
|
|
119
|
+
return urlObj?.toString();
|
|
72
120
|
};
|
|
73
121
|
|
|
74
122
|
const resolveSourcesContent = (data, url) => {
|
|
@@ -115,7 +163,7 @@ const resolveSourcesContent = (data, url) => {
|
|
|
115
163
|
const checkSourcesContent = (data) => {
|
|
116
164
|
const { sourcesContent, sources } = data;
|
|
117
165
|
|
|
118
|
-
if (!sourcesContent) {
|
|
166
|
+
if (!Array.isArray(sourcesContent)) {
|
|
119
167
|
data.sourcesContent = [];
|
|
120
168
|
return false;
|
|
121
169
|
}
|
|
@@ -136,15 +184,7 @@ const resolveSectionedSourceMap = (data, url, sections) => {
|
|
|
136
184
|
// offset: { line: 1, column: 0 },
|
|
137
185
|
// map: { sources, sourcesContent }
|
|
138
186
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (checkSourcesContent(map)) {
|
|
142
|
-
hasSourceContent = true;
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const done = resolveSourcesContent(map, url);
|
|
147
|
-
if (done) {
|
|
187
|
+
if (resolveSourceMap(item.map, url)) {
|
|
148
188
|
hasSourceContent = true;
|
|
149
189
|
}
|
|
150
190
|
|
|
@@ -159,6 +199,10 @@ const resolveSourceMap = (data, url) => {
|
|
|
159
199
|
if (!data) {
|
|
160
200
|
return;
|
|
161
201
|
}
|
|
202
|
+
// Optional metadata may be absent or malformed in otherwise usable maps.
|
|
203
|
+
if (!Array.isArray(data.names)) {
|
|
204
|
+
data.names = [];
|
|
205
|
+
}
|
|
162
206
|
const {
|
|
163
207
|
sections, sources, mappings
|
|
164
208
|
} = data;
|
|
@@ -167,7 +211,7 @@ const resolveSourceMap = (data, url) => {
|
|
|
167
211
|
return resolveSectionedSourceMap(data, url, sections);
|
|
168
212
|
}
|
|
169
213
|
|
|
170
|
-
if (!sources ||
|
|
214
|
+
if (!Array.isArray(sources) || typeof mappings !== 'string') {
|
|
171
215
|
return;
|
|
172
216
|
}
|
|
173
217
|
|
|
@@ -181,17 +225,6 @@ const resolveSourceMap = (data, url) => {
|
|
|
181
225
|
|
|
182
226
|
};
|
|
183
227
|
|
|
184
|
-
const getInlineSourceMap = (content) => {
|
|
185
|
-
let smc;
|
|
186
|
-
try {
|
|
187
|
-
smc = convertSourceMap.fromSource(content);
|
|
188
|
-
} catch (e) {
|
|
189
|
-
// ignore "//# sourceMappingURL=" in a string
|
|
190
|
-
// console.log(e);
|
|
191
|
-
}
|
|
192
|
-
return smc;
|
|
193
|
-
};
|
|
194
|
-
|
|
195
228
|
const collectSourceMaps = async (v8list, options) => {
|
|
196
229
|
|
|
197
230
|
const sourceList = [];
|
|
@@ -223,55 +256,60 @@ const collectSourceMaps = async (v8list, options) => {
|
|
|
223
256
|
sourceMap
|
|
224
257
|
};
|
|
225
258
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (type === 'js' && !sourceData.sourceMap) {
|
|
233
|
-
// from inline sync
|
|
234
|
-
const smc = getInlineSourceMap(source);
|
|
235
|
-
if (smc) {
|
|
236
|
-
sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
|
|
237
|
-
sourcemapList.push({
|
|
238
|
-
url,
|
|
239
|
-
inline: true
|
|
240
|
-
});
|
|
241
|
-
sourceList.push(sourceData);
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
// from url async
|
|
245
|
-
const sourceMapUrl = getSourceMapUrl(source, item.url);
|
|
246
|
-
if (sourceMapUrl) {
|
|
259
|
+
// An explicitly supplied, usable map takes precedence over sourceMappingURL.
|
|
260
|
+
// Keep the original source so V8 offsets remain valid.
|
|
261
|
+
if (type === 'js' && !isUsableSourceMap(sourceData.sourceMap)) {
|
|
262
|
+
delete sourceData.sourceMap;
|
|
263
|
+
const candidates = getSourceMapCandidates(source);
|
|
264
|
+
if (candidates.length) {
|
|
247
265
|
concurrency.addItem({
|
|
248
|
-
|
|
266
|
+
candidates,
|
|
249
267
|
sourceData
|
|
250
268
|
});
|
|
251
|
-
continue;
|
|
252
269
|
}
|
|
253
270
|
}
|
|
254
271
|
|
|
255
|
-
//
|
|
272
|
+
// Keep source entries in input order while maps load concurrently.
|
|
256
273
|
sourceList.push(sourceData);
|
|
257
274
|
|
|
258
275
|
}
|
|
259
276
|
|
|
260
|
-
// from url concurrency
|
|
261
277
|
await concurrency.start(async (item) => {
|
|
262
|
-
const {
|
|
278
|
+
const { candidates, sourceData } = item;
|
|
263
279
|
const { url } = sourceData;
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
for (const candidate of candidates) {
|
|
281
|
+
let data;
|
|
282
|
+
let sourceMapUrl;
|
|
283
|
+
try {
|
|
284
|
+
if (candidate.inline) {
|
|
285
|
+
data = convertSourceMap.fromComment(candidate.comment).sourcemap;
|
|
286
|
+
} else {
|
|
287
|
+
sourceMapUrl = resolveMapUrl(candidate.filename, url);
|
|
288
|
+
if (!sourceMapUrl) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
data = await loadSourceMap(sourceMapUrl, options);
|
|
292
|
+
}
|
|
293
|
+
if (!isUsableSourceMap(data)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const sourceMap = resolveSourceMap(data, url);
|
|
297
|
+
if (!sourceMap) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
sourceData.sourceMap = sourceMap;
|
|
301
|
+
sourcemapList.push(candidate.inline ? {
|
|
302
|
+
url,
|
|
303
|
+
inline: true
|
|
304
|
+
} : {
|
|
305
|
+
url,
|
|
306
|
+
sourceMapUrl
|
|
307
|
+
});
|
|
308
|
+
break;
|
|
309
|
+
} catch (e) {
|
|
310
|
+
// Try an earlier sourceMappingURL if parsing or loading fails.
|
|
311
|
+
}
|
|
271
312
|
}
|
|
272
|
-
|
|
273
|
-
sourceList.push(sourceData);
|
|
274
|
-
|
|
275
313
|
});
|
|
276
314
|
|
|
277
315
|
return {
|
|
@@ -906,6 +906,12 @@ const decodeSourceMappings = (state, originalDecodedMap) => {
|
|
|
906
906
|
// console.log(segment);
|
|
907
907
|
return;
|
|
908
908
|
}
|
|
909
|
+
// A malformed map may refer to a source outside its sources array.
|
|
910
|
+
// Check when mappings are already decoded, not during map selection.
|
|
911
|
+
const sourceMappings = originalDecodedMap.get(sourceIndex);
|
|
912
|
+
if (!sourceMappings) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
909
915
|
|
|
910
916
|
// 1-base
|
|
911
917
|
const generatedSN = generatedLine + 1;
|
|
@@ -936,7 +942,7 @@ const decodeSourceMappings = (state, originalDecodedMap) => {
|
|
|
936
942
|
}
|
|
937
943
|
|
|
938
944
|
allDecodedMappings.push(info);
|
|
939
|
-
|
|
945
|
+
sourceMappings.push(info);
|
|
940
946
|
|
|
941
947
|
// calculate line end column
|
|
942
948
|
if (!info.last) {
|
|
@@ -971,7 +977,7 @@ const decodeSourceMappings = (state, originalDecodedMap) => {
|
|
|
971
977
|
// console.log(generatedSN, generatedColumn, info, endColumn);
|
|
972
978
|
|
|
973
979
|
allDecodedMappings.push(endColumn);
|
|
974
|
-
|
|
980
|
+
sourceMappings.push(endColumn);
|
|
975
981
|
|
|
976
982
|
});
|
|
977
983
|
|
|
@@ -6,9 +6,6 @@ const { pathToFileURL, fileURLToPath } = require('url');
|
|
|
6
6
|
|
|
7
7
|
const { normalizeSourcePath, getSourcePathReplacer } = require('../utils/source-path.js');
|
|
8
8
|
|
|
9
|
-
// const EC = require('eight-colors');
|
|
10
|
-
const { convertSourceMap } = require('../packages/monocart-coverage-vendor.js');
|
|
11
|
-
|
|
12
9
|
// ========================================================================================================
|
|
13
10
|
|
|
14
11
|
const resolveAllDirList = (input) => {
|
|
@@ -133,11 +130,6 @@ const saveUntestedFileSource = async (entryFile, options) => {
|
|
|
133
130
|
sourceMap
|
|
134
131
|
};
|
|
135
132
|
|
|
136
|
-
// remove comments if not debug
|
|
137
|
-
if (!Util.isDebug()) {
|
|
138
|
-
sourceData.source = convertSourceMap.removeComments(source);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
133
|
// console.log('save untested file', id, url);
|
|
142
134
|
|
|
143
135
|
await Util.saveSourceCacheFile(sourceData, options);
|
package/lib/generate.js
CHANGED
|
@@ -321,9 +321,13 @@ const getInputData = async (mcr) => {
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
if (!dataList.length) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
if (!mcr.options.all) {
|
|
325
|
+
const dirs = inputList.map((dir) => Util.relativePath(dir));
|
|
326
|
+
Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// No coverage input: use V8 so `all` can add untested files.
|
|
330
|
+
dataList.push({ type: 'v8', data: [] });
|
|
327
331
|
}
|
|
328
332
|
|
|
329
333
|
return {
|