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