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 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, we can add empty coverage for untested files with option `all`, the untested files will show 0% coverage.
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
- 默认,未测试的文件是不会包含到覆盖率报告的,需要使用`all`选项来为这些文件添加一个空的覆盖率,也就是0%
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 getSourceMapUrl = (content, url) => {
52
-
53
- const m = content.match(convertSourceMap.mapFileCommentRegex);
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
- const map = item.map;
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 || !mappings) {
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
- // remove comments if not debug
227
- if (!Util.isDebug()) {
228
- sourceData.source = convertSourceMap.removeComments(source);
229
- }
230
-
231
- // check sourceMap only for js
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
- sourceMapUrl,
266
+ candidates,
249
267
  sourceData
250
268
  });
251
- continue;
252
269
  }
253
270
  }
254
271
 
255
- // no need check sourceMap
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 { sourceMapUrl, sourceData } = item;
278
+ const { candidates, sourceData } = item;
263
279
  const { url } = sourceData;
264
- const data = await loadSourceMap(sourceMapUrl, options);
265
- if (data) {
266
- sourceData.sourceMap = resolveSourceMap(data, url);
267
- sourcemapList.push({
268
- url,
269
- sourceMapUrl
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
- originalDecodedMap.get(sourceIndex).push(info);
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
- originalDecodedMap.get(sourceIndex).push(endColumn);
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
- const dirs = inputList.map((dir) => Util.relativePath(dir));
325
- Util.logError(`Not found coverage data in dir(s): ${dirs.join(', ')}`);
326
- return;
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 {