@@ -3,12 +3,18 @@ const {
3
3
ArrayPrototypeJoin,
4
4
ArrayPrototypeMap,
5
5
ArrayPrototypePush,
6
+ ArrayPrototypeReduce,
6
7
ObjectGetOwnPropertyDescriptor,
8
+ MathFloor,
9
+ MathMax,
10
+ MathMin,
7
11
NumberPrototypeToFixed,
8
12
SafePromiseAllReturnArrayLike,
9
13
RegExp,
10
14
RegExpPrototypeExec,
11
15
SafeMap,
16
+ StringPrototypePadStart,
17
+ StringPrototypePadEnd,
12
18
} = primordials ;
13
19
14
20
const { basename, relative } = require ( 'path' ) ;
@@ -27,6 +33,13 @@ const {
27
33
} = require ( 'internal/errors' ) ;
28
34
const { compose } = require ( 'stream' ) ;
29
35
36
+ const coverageColors = {
37
+ '__proto__' : null ,
38
+ 'high' : green ,
39
+ 'medium' : '\u001b[33m' ,
40
+ 'low' : red ,
41
+ } ;
42
+
30
43
const kMultipleCallbackInvocations = 'multipleCallbackInvocations' ;
31
44
const kRegExpPattern = / ^ \/ ( .* ) \/ ( [ a - z ] * ) $ / ;
32
45
const kSupportedFileExtensions = / \. [ c m ] ? j s $ / ;
@@ -250,45 +263,130 @@ function countCompletedTest(test, harness = test.root.harness) {
250
263
}
251
264
252
265
253
- function coverageThreshold ( coverage , color ) {
254
- coverage = NumberPrototypeToFixed ( coverage , 2 ) ;
255
- if ( color ) {
256
- if ( coverage > 90 ) return `${ green } ${ coverage } ${ color } ` ;
257
- if ( coverage < 50 ) return `${ red } ${ coverage } ${ color } ` ;
258
- }
259
- return coverage ;
266
+ function addTableLine ( prefix , width ) {
267
+ return `${ prefix } ${ '-' . repeat ( width ) } \n` ;
268
+ }
269
+
270
+ function truncateStart ( string , width ) {
271
+ return string . length > width ? `\u2026${ string . substring ( string . length - width + 1 , string . length ) } ` : string ;
272
+ }
273
+
274
+ function truncateEnd ( string , width ) {
275
+ return string . length > width ? `${ string . substring ( 0 , width - 1 ) } \u2026` : string ;
276
+ }
277
+
278
+ function formatLinesToRanges ( values ) {
279
+ return ArrayPrototypeMap ( ArrayPrototypeReduce ( values , ( prev , current , index , array ) => {
280
+ if ( ( index > 0 ) && ( ( current - array [ index - 1 ] ) === 1 ) ) {
281
+ prev [ prev . length - 1 ] [ 1 ] = current ;
282
+ } else {
283
+ prev . push ( [ current ] ) ;
284
+ }
285
+ return prev ;
286
+ } , [ ] ) , ( range ) => range . join ( '-' ) ) ;
287
+ }
288
+
289
+ function formatUncoveredLines ( lines , table ) {
290
+ if ( table ) return ArrayPrototypeJoin ( formatLinesToRanges ( lines ) , ' ' ) ;
291
+ return ArrayPrototypeJoin ( lines , ', ' ) ;
260
292
}
261
293
262
- function getCoverageReport ( pad , summary , symbol , color ) {
263
- let report = `${ color } ${ pad } ${ symbol } start of coverage report\n` ;
294
+ const kColumns = [ 'line %' , 'branch %' , 'funcs %' ] ;
295
+ const kColumnsKeys = [ 'coveredLinePercent' , 'coveredBranchPercent' , 'coveredFunctionPercent' ] ;
296
+ const kSeparator = ' | ' ;
297
+
298
+ function getCoverageReport ( pad , summary , symbol , color , table ) {
299
+ const prefix = `${ pad } ${ symbol } ` ;
300
+ let report = `${ color } ${ prefix } start of coverage report\n` ;
301
+
302
+ let filePadLength ;
303
+ let columnPadLengths = [ ] ;
304
+ let uncoveredLinesPadLength ;
305
+ let tableWidth ;
306
+
307
+ if ( table ) {
308
+ // Get expected column sizes
309
+ filePadLength = table && ArrayPrototypeReduce ( summary . files , ( acc , file ) =>
310
+ MathMax ( acc , relative ( summary . workingDirectory , file . path ) . length ) , 0 ) ;
311
+ filePadLength = MathMax ( filePadLength , 'file' . length ) ;
312
+ const fileWidth = filePadLength + 2 ;
313
+
314
+ columnPadLengths = ArrayPrototypeMap ( kColumns , ( column ) => ( table ? MathMax ( column . length , 6 ) : 0 ) ) ;
315
+ const columnsWidth = ArrayPrototypeReduce ( columnPadLengths , ( acc , columnPadLength ) => acc + columnPadLength + 3 , 0 ) ;
316
+
317
+ uncoveredLinesPadLength = table && ArrayPrototypeReduce ( summary . files , ( acc , file ) =>
318
+ MathMax ( acc , formatUncoveredLines ( file . uncoveredLineNumbers , table ) . length ) , 0 ) ;
319
+ uncoveredLinesPadLength = MathMax ( uncoveredLinesPadLength , 'uncovered lines' . length ) ;
320
+ const uncoveredLinesWidth = uncoveredLinesPadLength + 2 ;
321
+
322
+ tableWidth = fileWidth + columnsWidth + uncoveredLinesWidth ;
323
+
324
+ // Fit with sensible defaults
325
+ const availableWidth = ( process . stdout . columns || 9000 ) - prefix . length ;
326
+ const columnsExtras = tableWidth - availableWidth ;
327
+ if ( table && columnsExtras > 0 ) {
328
+ // Ensure file name is sufficiently visible
329
+ const minFilePad = MathMin ( 8 , filePadLength ) ;
330
+ filePadLength -= MathFloor ( columnsExtras * 0.2 ) ;
331
+ filePadLength = MathMax ( filePadLength , minFilePad ) ;
332
+
333
+ // Get rest of available space, subtracting margins
334
+ uncoveredLinesPadLength = MathMax ( availableWidth - columnsWidth - ( filePadLength + 2 ) - 2 , 1 ) ;
335
+
336
+ // Update table width
337
+ tableWidth = availableWidth ;
338
+ } else {
339
+ uncoveredLinesPadLength = Infinity ;
340
+ }
341
+ }
342
+
343
+
344
+ function getCell ( string , width , { pad, truncate, coverage } ) {
345
+ if ( ! table ) return string ;
346
+
347
+ let result = string ;
348
+ if ( pad ) result = pad ( result , width ) ;
349
+ if ( truncate ) result = truncate ( result , width ) ;
350
+ if ( color && coverage !== undefined ) {
351
+ if ( coverage > 90 ) return `${ coverageColors . high } ${ result } ${ color } ` ;
352
+ if ( coverage > 50 ) return `${ coverageColors . medium } ${ result } ${ color } ` ;
353
+ return `${ coverageColors . low } ${ result } ${ color } ` ;
354
+ }
355
+ return result ;
356
+ }
264
357
265
- report += `${ pad } ${ symbol } file | line % | branch % | funcs % | uncovered lines\n` ;
358
+ // Head
359
+ if ( table ) report += addTableLine ( prefix , tableWidth ) ;
360
+ report += `${ prefix } ${ getCell ( 'file' , filePadLength , { pad : StringPrototypePadEnd , truncate : truncateEnd } ) } ${ kSeparator } ` +
361
+ `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( kColumns , ( column , i ) => getCell ( column , columnPadLengths [ i ] , { pad : StringPrototypePadStart } ) ) , kSeparator ) } ${ kSeparator } ` +
362
+ `${ getCell ( 'uncovered lines' , uncoveredLinesPadLength , { truncate : truncateEnd } ) } \n` ;
363
+ if ( table ) report += addTableLine ( prefix , tableWidth ) ;
266
364
365
+ // Body
267
366
for ( let i = 0 ; i < summary . files . length ; ++ i ) {
268
- const {
269
- path,
270
- coveredLinePercent,
271
- coveredBranchPercent,
272
- coveredFunctionPercent,
273
- uncoveredLineNumbers,
274
- } = summary . files [ i ] ;
275
- const relativePath = relative ( summary . workingDirectory , path ) ;
276
- const lines = coverageThreshold ( coveredLinePercent , color ) ;
277
- const branches = coverageThreshold ( coveredBranchPercent , color ) ;
278
- const functions = coverageThreshold ( coveredFunctionPercent , color ) ;
279
- const uncovered = ArrayPrototypeJoin ( uncoveredLineNumbers , ', ' ) ;
280
-
281
- report += `${ pad } ${ symbol } ${ relativePath } | ${ lines } | ${ branches } | ` +
282
- `${ functions } | ${ uncovered } \n` ;
367
+ const file = summary . files [ i ] ;
368
+ const relativePath = relative ( summary . workingDirectory , file . path ) ;
369
+
370
+ let fileCoverage = 0 ;
371
+ const coverages = ArrayPrototypeMap ( kColumnsKeys , ( columnKey ) => {
372
+ const percent = file [ columnKey ] ;
373
+ fileCoverage += percent ;
374
+ return percent ;
375
+ } ) ;
376
+ fileCoverage /= kColumnsKeys . length ;
377
+
378
+ report += `${ prefix } ${ getCell ( relativePath , filePadLength , { pad : StringPrototypePadEnd , truncate : truncateStart , coverage : fileCoverage } ) } ${ kSeparator } ` +
379
+ `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( coverages , ( coverage , j ) => getCell ( NumberPrototypeToFixed ( coverage , 2 ) , columnPadLengths [ j ] , { coverage, pad : StringPrototypePadStart } ) ) , kSeparator ) } ${ kSeparator } ` +
380
+ `${ getCell ( formatUncoveredLines ( file . uncoveredLineNumbers , table ) , uncoveredLinesPadLength , { truncate : truncateEnd } ) } \n` ;
283
381
}
284
382
285
- const { totals } = summary ;
286
- report += ` ${ pad } ${ symbol } all files | ` +
287
- ` ${ coverageThreshold ( totals . coveredLinePercent , color ) } | ` +
288
- `${ coverageThreshold ( totals . coveredBranchPercent , color ) } | ` +
289
- ` ${ coverageThreshold ( totals . coveredFunctionPercent , color ) } |\n` ;
383
+ // Foot
384
+ if ( table ) report += addTableLine ( prefix , tableWidth ) ;
385
+ report += ` ${ prefix } ${ getCell ( 'all files' , filePadLength , { pad : StringPrototypePadEnd , truncate : truncateEnd } ) } ${ kSeparator } ` +
386
+ `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( kColumnsKeys , ( columnKey , j ) => getCell ( NumberPrototypeToFixed ( summary . totals [ columnKey ] , 2 ) , columnPadLengths [ j ] , { coverage : summary . totals [ columnKey ] , pad : StringPrototypePadStart } ) ) , kSeparator ) } |\n` ;
387
+ if ( table ) report += addTableLine ( prefix , tableWidth ) ;
290
388
291
- report += `${ pad } ${ symbol } end of coverage report\n` ;
389
+ report += `${ prefix } end of coverage report\n` ;
292
390
if ( color ) {
293
391
report += white ;
294
392
}
0 commit comments