monocart-coverage-reports 2.13.1 → 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.
@@ -1,7 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const EC = require('eight-colors');
4
- const { CommentParser } = require('monocart-locator');
5
4
  const { fileURLToPath, pathToFileURL } = require('url');
6
5
  const Concurrency = require('../platform/concurrency.js');
7
6
  const { convertSourceMap } = require('../packages/monocart-coverage-vendor.js');
@@ -9,15 +8,72 @@ const { flattenSourceMaps } = require('./flatten-source-maps.js');
9
8
 
10
9
  const Util = require('../utils/util.js');
11
10
 
12
- const getSourceMapComments = (source) => {
11
+ const getSourceMapCandidates = (source) => {
13
12
  if (!source.includes('sourceMappingURL=')) {
14
13
  return [];
15
14
  }
16
- return new CommentParser(source).comments
17
- .map((item) => item.text)
18
- .filter((comment) => (/^\/[/*][@#][ \t]+sourceMappingURL=/).test(comment));
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;
19
73
  };
20
74
 
75
+ const isUsableSourceMap = (data) => isValidSourceMap(data) && hasMappings(data);
76
+
21
77
  const defaultSourceMapResolver = async (url = '') => {
22
78
 
23
79
  if (url.startsWith('file:')) {
@@ -63,24 +119,6 @@ const resolveMapUrl = (filename, url) => {
63
119
  return urlObj?.toString();
64
120
  };
65
121
 
66
- const getSourceMapUrl = (comments, url) => {
67
- for (let i = comments.length - 1; i >= 0; i--) {
68
- const r = convertSourceMap.mapFileCommentRegex.exec(comments[i]);
69
- if (!r || r.index !== 0) {
70
- continue;
71
- }
72
- // //# captures in 1 and /* */ in 2
73
- const filename = r[1] || r[2];
74
- if (!filename || filename.startsWith('data:')) {
75
- continue;
76
- }
77
- const mapUrl = resolveMapUrl(filename, url);
78
- if (mapUrl) {
79
- return mapUrl;
80
- }
81
- }
82
- };
83
-
84
122
  const resolveSourcesContent = (data, url) => {
85
123
 
86
124
  const { sources, sourcesContent } = data;
@@ -125,7 +163,7 @@ const resolveSourcesContent = (data, url) => {
125
163
  const checkSourcesContent = (data) => {
126
164
  const { sourcesContent, sources } = data;
127
165
 
128
- if (!sourcesContent) {
166
+ if (!Array.isArray(sourcesContent)) {
129
167
  data.sourcesContent = [];
130
168
  return false;
131
169
  }
@@ -146,15 +184,7 @@ const resolveSectionedSourceMap = (data, url, sections) => {
146
184
  // offset: { line: 1, column: 0 },
147
185
  // map: { sources, sourcesContent }
148
186
 
149
- const map = item.map;
150
-
151
- if (checkSourcesContent(map)) {
152
- hasSourceContent = true;
153
- return;
154
- }
155
-
156
- const done = resolveSourcesContent(map, url);
157
- if (done) {
187
+ if (resolveSourceMap(item.map, url)) {
158
188
  hasSourceContent = true;
159
189
  }
160
190
 
@@ -169,6 +199,10 @@ const resolveSourceMap = (data, url) => {
169
199
  if (!data) {
170
200
  return;
171
201
  }
202
+ // Optional metadata may be absent or malformed in otherwise usable maps.
203
+ if (!Array.isArray(data.names)) {
204
+ data.names = [];
205
+ }
172
206
  const {
173
207
  sections, sources, mappings
174
208
  } = data;
@@ -177,7 +211,7 @@ const resolveSourceMap = (data, url) => {
177
211
  return resolveSectionedSourceMap(data, url, sections);
178
212
  }
179
213
 
180
- if (!sources || !mappings) {
214
+ if (!Array.isArray(sources) || typeof mappings !== 'string') {
181
215
  return;
182
216
  }
183
217
 
@@ -191,21 +225,6 @@ const resolveSourceMap = (data, url) => {
191
225
 
192
226
  };
193
227
 
194
- const getInlineSourceMap = (comments) => {
195
- for (let i = comments.length - 1; i >= 0; i--) {
196
- const comment = comments[i];
197
- const match = convertSourceMap.commentRegex.exec(comment);
198
- if (!match || match.index !== 0) {
199
- continue;
200
- }
201
- try {
202
- return convertSourceMap.fromComment(comment);
203
- } catch (e) {
204
- // Ignore invalid inline maps.
205
- }
206
- }
207
- };
208
-
209
228
  const collectSourceMaps = async (v8list, options) => {
210
229
 
211
230
  const sourceList = [];
@@ -237,52 +256,60 @@ const collectSourceMaps = async (v8list, options) => {
237
256
  sourceMap
238
257
  };
239
258
 
259
+ // An explicitly supplied, usable map takes precedence over sourceMappingURL.
240
260
  // Keep the original source so V8 offsets remain valid.
241
- // check sourceMap only for js
242
- if (type === 'js' && !sourceData.sourceMap) {
243
- const comments = getSourceMapComments(source);
244
- // from inline sync
245
- const smc = getInlineSourceMap(comments);
246
- if (smc) {
247
- sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
248
- sourcemapList.push({
249
- url,
250
- inline: true
251
- });
252
- sourceList.push(sourceData);
253
- continue;
254
- }
255
- // from url async
256
- const sourceMapUrl = getSourceMapUrl(comments, url);
257
- if (sourceMapUrl) {
261
+ if (type === 'js' && !isUsableSourceMap(sourceData.sourceMap)) {
262
+ delete sourceData.sourceMap;
263
+ const candidates = getSourceMapCandidates(source);
264
+ if (candidates.length) {
258
265
  concurrency.addItem({
259
- sourceMapUrl,
266
+ candidates,
260
267
  sourceData
261
268
  });
262
- continue;
263
269
  }
264
270
  }
265
271
 
266
- // no need check sourceMap
272
+ // Keep source entries in input order while maps load concurrently.
267
273
  sourceList.push(sourceData);
268
274
 
269
275
  }
270
276
 
271
- // from url concurrency
272
277
  await concurrency.start(async (item) => {
273
- const { sourceMapUrl, sourceData } = item;
278
+ const { candidates, sourceData } = item;
274
279
  const { url } = sourceData;
275
- const data = await loadSourceMap(sourceMapUrl, options);
276
- if (data) {
277
- sourceData.sourceMap = resolveSourceMap(data, url);
278
- sourcemapList.push({
279
- url,
280
- sourceMapUrl
281
- });
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
+ }
282
312
  }
283
-
284
- sourceList.push(sourceData);
285
-
286
313
  });
287
314
 
288
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monocart-coverage-reports",
3
- "version": "2.13.1",
3
+ "version": "2.13.2",
4
4
  "description": "A code coverage tool to generate native V8 reports or Istanbul reports.",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {