@@ -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 $ / ;
@@ -250,45 +263,130 @@ function countCompletedTest(test, harness = test.root.harness) {
250263}
251264
252265
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 , ', ' ) ;
260292}
261293
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+ }
264357
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 ) ;
266364
365+ // Body
267366 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` ;
283381 }
284382
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 ) ;
290388
291- report += `${ pad } ${ symbol } end of coverage report\n` ;
389+ report += `${ prefix } end of coverage report\n` ;
292390 if ( color ) {
293391 report += white ;
294392 }
0 commit comments