@@ -3,8 +3,8 @@ const furi = require('furi')
3
3
const libCoverage = require ( 'istanbul-lib-coverage' )
4
4
const libReport = require ( 'istanbul-lib-report' )
5
5
const reports = require ( 'istanbul-reports' )
6
- const { readdirSync, readFileSync } = require ( 'fs' )
7
- const { isAbsolute, resolve } = require ( 'path' )
6
+ const { readdirSync, readFileSync, statSync } = require ( 'fs' )
7
+ const { isAbsolute, resolve, join , relative , extname , dirname } = require ( 'path' )
8
8
// TODO: switch back to @c 88/v8-coverage once patch is landed.
9
9
const { mergeProcessCovs } = require ( '@bcoe/v8-coverage' )
10
10
const v8toIstanbul = require ( 'v8-to-istanbul' )
@@ -20,7 +20,8 @@ class Report {
20
20
watermarks,
21
21
omitRelative,
22
22
wrapperLength,
23
- resolve : resolvePaths
23
+ resolve : resolvePaths ,
24
+ all
24
25
} ) {
25
26
this . reporter = reporter
26
27
this . reportsDirectory = reportsDirectory
@@ -34,6 +35,8 @@ class Report {
34
35
this . omitRelative = omitRelative
35
36
this . sourceMapCache = { }
36
37
this . wrapperLength = wrapperLength
38
+ this . all = all
39
+ this . src = process . cwd ( )
37
40
}
38
41
39
42
async run ( ) {
@@ -57,8 +60,8 @@ class Report {
57
60
// use-case.
58
61
if ( this . _allCoverageFiles ) return this . _allCoverageFiles
59
62
63
+ const map = libCoverage . createCoverageMap ( )
60
64
const v8ProcessCov = this . _getMergedProcessCov ( )
61
- const map = libCoverage . createCoverageMap ( { } )
62
65
const resultCountPerPath = new Map ( )
63
66
const possibleCjsEsmBridges = new Map ( )
64
67
@@ -95,11 +98,45 @@ class Report {
95
98
map . merge ( converter . toIstanbul ( ) )
96
99
}
97
100
}
98
-
99
101
this . _allCoverageFiles = map
100
102
return this . _allCoverageFiles
101
103
}
102
104
105
+ /**
106
+ * v8toIstanbul will return full paths for js files, but in cases where a sourcemap is involved (ts etc)
107
+ * it will return a relative path. Normally this is fine, but when using the `--all` option we load files
108
+ * in advance and index them by a path. Here we need to decide in advance if we'll handle full or relative
109
+ * urls. This function gets Istanbul CoverageMapData and makes sure all paths are relative when --all is
110
+ * supplied.
111
+ * @param {V8ToIstanbul } converter coverts v8 coverage to Istanbul's format
112
+ * @param {Map<string,boolean> } allFilesMap a map of files for the project that allows us to track
113
+ * if a file has coverage
114
+ * @returns {CoverageMapData }
115
+ * @private
116
+ */
117
+ _getIstanbulCoverageMap ( converter , allFilesMap ) {
118
+ const istanbulCoverage = converter . toIstanbul ( )
119
+ const mappedPath = Object . keys ( istanbulCoverage ) [ 0 ]
120
+ if ( this . all && isAbsolute ( mappedPath ) ) {
121
+ const coverageData = istanbulCoverage [ mappedPath ]
122
+ const relativeFile = this . relativeToSrc ( mappedPath )
123
+ const relativePathClone = {
124
+ [ relativeFile ] : coverageData
125
+ }
126
+ allFilesMap . set ( relativeFile , true )
127
+ return relativePathClone
128
+ } else if ( this . all ) {
129
+ allFilesMap . set ( mappedPath , true )
130
+ return istanbulCoverage
131
+ } else {
132
+ return istanbulCoverage
133
+ }
134
+ }
135
+
136
+ relativeToSrc ( file ) {
137
+ return join ( this . src , relative ( this . src , file ) )
138
+ }
139
+
103
140
/**
104
141
* Returns source-map and fake source file, if cached during Node.js'
105
142
* execution. This is used to support tools like ts-node, which transpile
@@ -128,6 +165,29 @@ class Report {
128
165
return sources
129
166
}
130
167
168
+ /**
169
+ * //TODO: use https://www.npmjs.com/package/convert-source-map
170
+ * // no need to roll this ourselves this is already in the dep tree
171
+ * https://sourcemaps.info/spec.html
172
+ * @param {String } compilation target file
173
+ * @returns {String } full path to source map file
174
+ * @private
175
+ */
176
+ _getSourceMapFromFile ( file ) {
177
+ const fileBody = readFileSync ( file ) . toString ( )
178
+ const sourceMapLineRE = / \/ \/ [ # @ ] ? s o u r c e M a p p i n g U R L = ( [ ^ \s ' " ] + ) \s * $ / mg
179
+ const results = fileBody . match ( sourceMapLineRE )
180
+ if ( results !== null ) {
181
+ const sourceMap = results [ results . length - 1 ] . split ( '=' ) [ 1 ]
182
+ if ( isAbsolute ( sourceMap ) ) {
183
+ return sourceMap
184
+ } else {
185
+ const base = dirname ( file )
186
+ return join ( base , sourceMap )
187
+ }
188
+ }
189
+ }
190
+
131
191
/**
132
192
* Returns the merged V8 process coverage.
133
193
*
@@ -139,17 +199,160 @@ class Report {
139
199
*/
140
200
_getMergedProcessCov ( ) {
141
201
const v8ProcessCovs = [ ]
202
+ const fileIndex = new Map ( ) // Map<string, bool>
142
203
for ( const v8ProcessCov of this . _loadReports ( ) ) {
143
204
if ( this . _isCoverageObject ( v8ProcessCov ) ) {
144
205
if ( v8ProcessCov [ 'source-map-cache' ] ) {
145
206
Object . assign ( this . sourceMapCache , v8ProcessCov [ 'source-map-cache' ] )
146
207
}
147
- v8ProcessCovs . push ( this . _normalizeProcessCov ( v8ProcessCov ) )
208
+ v8ProcessCovs . push ( this . _normalizeProcessCov ( v8ProcessCov , fileIndex ) )
148
209
}
149
210
}
211
+
212
+ if ( this . all ) {
213
+ const emptyReports = [ ]
214
+ v8ProcessCovs . unshift ( {
215
+ result : emptyReports
216
+ } )
217
+ const workingDir = process . cwd ( )
218
+ this . exclude . globSync ( workingDir ) . forEach ( ( f ) => {
219
+ const fullPath = resolve ( workingDir , f )
220
+ if ( ! fileIndex . has ( fullPath ) ) {
221
+ const ext = extname ( f )
222
+ if ( ext === '.js' || ext === '.ts' || ext === '.mjs' ) {
223
+ const stat = statSync ( f )
224
+ const sourceMap = this . _getSourceMapFromFile ( f )
225
+ if ( sourceMap !== undefined ) {
226
+ this . sourceMapCache [ `file://${ fullPath } ` ] = { data : JSON . parse ( readFileSync ( sourceMap ) . toString ( ) ) }
227
+ }
228
+ emptyReports . push ( {
229
+ scriptId : 0 ,
230
+ url : resolve ( f ) ,
231
+ functions : [ {
232
+ functionName : '(empty-report)' ,
233
+ ranges : [ {
234
+ startOffset : 0 ,
235
+ endOffset : stat . size ,
236
+ count : 0
237
+ } ] ,
238
+ isBlockCoverage : true
239
+ } ]
240
+ } )
241
+ }
242
+ }
243
+ } )
244
+ }
245
+
150
246
return mergeProcessCovs ( v8ProcessCovs )
151
247
}
152
248
249
+ /**
250
+ * If --all is supplied we need to fetch a list of files that respects
251
+ * include/exclude that will be used to see the coverage report with
252
+ * empty results for unloaded files
253
+ * @returns {Array.<string> }
254
+ */
255
+ getFileListForAll ( ) {
256
+ return this . exclude . globSync ( this . src ) . reduce ( ( allFileList , file ) => {
257
+ const srcPath = join ( this . src , file )
258
+ allFileList . set ( srcPath , false )
259
+ return allFileList
260
+ } , new Map ( ) )
261
+ }
262
+
263
+ /**
264
+ * Iterates over the entries of `allFilesMap` and where an entries' boolean
265
+ * value is false, generate an empty coverage record for the file in question.
266
+ * @param {Map<string, boolean> } allFilesMap where the key is the path to a file
267
+ * read by `--all` and the boolean value indicates whether a coverage record
268
+ * for this file was found.
269
+ * @param {CoverageMap } coverageMap A coverage map produced from v8's output.
270
+ * If we encounter an unloaded file, it is merged into this CoverageMap
271
+ * @returns {Promise.<undefined> }
272
+ * @private
273
+ */
274
+ async _createEmptyRecordsForUnloadedFiles ( allFilesMap , coverageMap ) {
275
+ for ( const [ path , seen ] of allFilesMap . entries ( ) ) {
276
+ // if value is false, that means we didn't receive a coverage
277
+ // record. Create and merge an empty record for the file
278
+ if ( seen === false ) {
279
+ const emptyCoverageMap = await this . _getEmpyCoverageResultForFile ( path )
280
+ coverageMap . merge ( emptyCoverageMap )
281
+ }
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Uses `v8toIstanbul` to create CoverageMapData for a file with all statements,
287
+ * functions and branches set to unreached
288
+ * @param {string } fullPath
289
+ * @returns {Promise.<CoverageMapData> }
290
+ * @private
291
+ */
292
+ async _getEmpyCoverageResultForFile ( fullPath ) {
293
+ const converter = v8toIstanbul ( fullPath , this . wrapperLength )
294
+ await converter . load ( )
295
+ const initialCoverage = converter . toIstanbul ( )
296
+ this . _setCoverageMapToUncovered ( Object . values ( initialCoverage ) [ 0 ] )
297
+ return initialCoverage
298
+ }
299
+
300
+ /**
301
+ * v8ToIstanbul will initialize statements to covered until demonstrated to
302
+ * be uncovered. In addition, reporters will interpret empty branch and
303
+ * function counters as 100%. Here we reset line coverage to 0% and create
304
+ * a fake stub entry for branch/functions that will be interpreted as 0%
305
+ * coverage.
306
+ * @param {CoverageMapData } coverageMap
307
+ * @private
308
+ */
309
+ _setCoverageMapToUncovered ( coverageMap ) {
310
+ Object . keys ( coverageMap . s ) . forEach ( ( key ) => {
311
+ coverageMap . s [ key ] = 0
312
+ } )
313
+
314
+ coverageMap . b = {
315
+ 0 : [
316
+ 0
317
+ ]
318
+ }
319
+
320
+ coverageMap . branchMap = {
321
+ 0 : {
322
+ locations : [ ]
323
+ }
324
+ }
325
+
326
+ coverageMap . fnMap = {
327
+ 0 : {
328
+ decl : {
329
+ start : {
330
+ line : 0 ,
331
+ column : 0
332
+ } ,
333
+ end : {
334
+ line : 0 ,
335
+ columns : 0
336
+ }
337
+ } ,
338
+ loc : {
339
+ start : {
340
+ line : 0 ,
341
+ column : 0
342
+ } ,
343
+ end : {
344
+ line : 0 ,
345
+ columns : 0
346
+ }
347
+ }
348
+ }
349
+ }
350
+
351
+ coverageMap . f = {
352
+ 0 : false
353
+ }
354
+ }
355
+
153
356
/**
154
357
* Make sure v8ProcessCov actually contains coverage information.
155
358
*
@@ -196,12 +399,13 @@ class Report {
196
399
* @return {v8ProcessCov } Normalized V8 process coverage.
197
400
* @private
198
401
*/
199
- _normalizeProcessCov ( v8ProcessCov ) {
402
+ _normalizeProcessCov ( v8ProcessCov , fileIndex ) {
200
403
const result = [ ]
201
404
for ( const v8ScriptCov of v8ProcessCov . result ) {
202
405
if ( / ^ f i l e : \/ \/ / . test ( v8ScriptCov . url ) ) {
203
406
try {
204
407
v8ScriptCov . url = furi . toSysPath ( v8ScriptCov . url )
408
+ fileIndex . set ( v8ScriptCov . url , true )
205
409
} catch ( err ) {
206
410
console . warn ( err )
207
411
continue
0 commit comments