@@ -3,20 +3,28 @@ 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, 
18+   StringPrototypeRepeat, 
19+   StringPrototypeSlice, 
1220}  =  primordials ; 
1321
1422const  {  basename,  relative }  =  require ( 'path' ) ; 
1523const  {  createWriteStream }  =  require ( 'fs' ) ; 
1624const  {  pathToFileURL }  =  require ( 'internal/url' ) ; 
1725const  {  createDeferredPromise }  =  require ( 'internal/util' ) ; 
1826const  {  getOptionValue }  =  require ( 'internal/options' ) ; 
19- const  {  green,  red,  white,  shouldColorize }  =  require ( 'internal/util/colors' ) ; 
27+ const  {  green,  yellow ,   red,  white,  shouldColorize }  =  require ( 'internal/util/colors' ) ; 
2028
2129const  { 
2230  codes : { 
@@ -27,6 +35,13 @@ const {
2735}  =  require ( 'internal/errors' ) ; 
2836const  {  compose }  =  require ( 'stream' ) ; 
2937
38+ const  coverageColors  =  { 
39+   __proto__ : null , 
40+   high : green , 
41+   medium : yellow , 
42+   low : red , 
43+ } ; 
44+ 
3045const  kMultipleCallbackInvocations  =  'multipleCallbackInvocations' ; 
3146const  kRegExpPattern  =  / ^ \/ ( .* ) \/ ( [ a - z ] * ) $ / ; 
3247const  kSupportedFileExtensions  =  / \. [ c m ] ? j s $ / ; 
@@ -256,45 +271,139 @@ function countCompletedTest(test, harness = test.root.harness) {
256271} 
257272
258273
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 }  ` ; 
274+ const  memo  =  new  SafeMap ( ) ; 
275+ function  addTableLine ( prefix ,  width )  { 
276+   const  key  =  `${ prefix }  -${ width }  ` ; 
277+   let  value  =  memo . get ( key ) ; 
278+   if  ( value  ===  undefined )  { 
279+     value  =  `${ prefix } ${ StringPrototypeRepeat ( '-' ,  width ) }  \n` ; 
280+     memo . set ( key ,  value ) ; 
264281  } 
265-   return  coverage ; 
282+ 
283+   return  value ; 
284+ } 
285+ 
286+ const  kHorizontalEllipsis  =  '\u2026' ; 
287+ function  truncateStart ( string ,  width )  { 
288+   return  string . length  >  width  ? `${ kHorizontalEllipsis } ${ StringPrototypeSlice ( string ,  string . length  -  width  +  1 ) }  `  : string ; 
289+ } 
290+ 
291+ function  truncateEnd ( string ,  width )  { 
292+   return  string . length  >  width  ? `${ StringPrototypeSlice ( string ,  0 ,  width  -  1 ) } ${ kHorizontalEllipsis }  `  : string ; 
293+ } 
294+ 
295+ function  formatLinesToRanges ( values )  { 
296+   return  ArrayPrototypeMap ( ArrayPrototypeReduce ( values ,  ( prev ,  current ,  index ,  array )  =>  { 
297+     if  ( ( index  >  0 )  &&  ( ( current  -  array [ index  -  1 ] )  ===  1 ) )  { 
298+       prev [ prev . length  -  1 ] [ 1 ]  =  current ; 
299+     }  else  { 
300+       prev . push ( [ current ] ) ; 
301+     } 
302+     return  prev ; 
303+   } ,  [ ] ) ,  ( range )  =>  ArrayPrototypeJoin ( range ,  '-' ) ) ; 
304+ } 
305+ 
306+ function  formatUncoveredLines ( lines ,  table )  { 
307+   if  ( table )  return  ArrayPrototypeJoin ( formatLinesToRanges ( lines ) ,  ' ' ) ; 
308+   return  ArrayPrototypeJoin ( lines ,  ', ' ) ; 
266309} 
267310
268- function  getCoverageReport ( pad ,  summary ,  symbol ,  color )  { 
269-   let  report  =  `${ color } ${ pad } ${ symbol }  start of coverage report\n` ; 
311+ const  kColumns  =  [ 'line %' ,  'branch %' ,  'funcs %' ] ; 
312+ const  kColumnsKeys  =  [ 'coveredLinePercent' ,  'coveredBranchPercent' ,  'coveredFunctionPercent' ] ; 
313+ const  kSeparator  =  ' | ' ; 
314+ 
315+ function  getCoverageReport ( pad ,  summary ,  symbol ,  color ,  table )  { 
316+   const  prefix  =  `${ pad } ${ symbol }  ` ; 
317+   let  report  =  `${ color } ${ prefix }  start of coverage report\n` ; 
318+ 
319+   let  filePadLength ; 
320+   let  columnPadLengths  =  [ ] ; 
321+   let  uncoveredLinesPadLength ; 
322+   let  tableWidth ; 
323+ 
324+   if  ( table )  { 
325+     // Get expected column sizes 
326+     filePadLength  =  table  &&  ArrayPrototypeReduce ( summary . files ,  ( acc ,  file )  => 
327+       MathMax ( acc ,  relative ( summary . workingDirectory ,  file . path ) . length ) ,  0 ) ; 
328+     filePadLength  =  MathMax ( filePadLength ,  'file' . length ) ; 
329+     const  fileWidth  =  filePadLength  +  2 ; 
330+ 
331+     columnPadLengths  =  ArrayPrototypeMap ( kColumns ,  ( column )  =>  ( table  ? MathMax ( column . length ,  6 )  : 0 ) ) ; 
332+     const  columnsWidth  =  ArrayPrototypeReduce ( columnPadLengths ,  ( acc ,  columnPadLength )  =>  acc  +  columnPadLength  +  3 ,  0 ) ; 
333+ 
334+     uncoveredLinesPadLength  =  table  &&  ArrayPrototypeReduce ( summary . files ,  ( acc ,  file )  => 
335+       MathMax ( acc ,  formatUncoveredLines ( file . uncoveredLineNumbers ,  table ) . length ) ,  0 ) ; 
336+     uncoveredLinesPadLength  =  MathMax ( uncoveredLinesPadLength ,  'uncovered lines' . length ) ; 
337+     const  uncoveredLinesWidth  =  uncoveredLinesPadLength  +  2 ; 
338+ 
339+     tableWidth  =  fileWidth  +  columnsWidth  +  uncoveredLinesWidth ; 
340+ 
341+     // Fit with sensible defaults 
342+     const  availableWidth  =  ( process . stdout . columns  ||  Infinity )  -  prefix . length ; 
343+     const  columnsExtras  =  tableWidth  -  availableWidth ; 
344+     if  ( table  &&  columnsExtras  >  0 )  { 
345+       // Ensure file name is sufficiently visible 
346+       const  minFilePad  =  MathMin ( 8 ,  filePadLength ) ; 
347+       filePadLength  -=  MathFloor ( columnsExtras  *  0.2 ) ; 
348+       filePadLength  =  MathMax ( filePadLength ,  minFilePad ) ; 
349+ 
350+       // Get rest of available space, subtracting margins 
351+       uncoveredLinesPadLength  =  MathMax ( availableWidth  -  columnsWidth  -  ( filePadLength  +  2 )  -  2 ,  1 ) ; 
352+ 
353+       // Update table width 
354+       tableWidth  =  availableWidth ; 
355+     }  else  { 
356+       uncoveredLinesPadLength  =  Infinity ; 
357+     } 
358+   } 
359+ 
360+ 
361+   function  getCell ( string ,  width ,  pad ,  truncate ,  coverage )  { 
362+     if  ( ! table )  return  string ; 
363+ 
364+     let  result  =  string ; 
365+     if  ( pad )  result  =  pad ( result ,  width ) ; 
366+     if  ( truncate )  result  =  truncate ( result ,  width ) ; 
367+     if  ( color  &&  coverage  !==  undefined )  { 
368+       if  ( coverage  >  90 )  return  `${ coverageColors . high } ${ result } ${ color }  ` ; 
369+       if  ( coverage  >  50 )  return  `${ coverageColors . medium } ${ result } ${ color }  ` ; 
370+       return  `${ coverageColors . low } ${ result } ${ color }  ` ; 
371+     } 
372+     return  result ; 
373+   } 
270374
271-   report  +=  `${ pad } ${ symbol }  file | line % | branch % | funcs % | uncovered lines\n` ; 
375+   // Head 
376+   if  ( table )  report  +=  addTableLine ( prefix ,  tableWidth ) ; 
377+   report  +=  `${ prefix } ${ getCell ( 'file' ,  filePadLength ,  StringPrototypePadEnd ,  truncateEnd ) } ${ kSeparator }  `  + 
378+             `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( kColumns ,  ( column ,  i )  =>  getCell ( column ,  columnPadLengths [ i ] ,  StringPrototypePadStart ) ) ,  kSeparator ) } ${ kSeparator }  `  + 
379+             `${ getCell ( 'uncovered lines' ,  uncoveredLinesPadLength ,  false ,  truncateEnd ) }  \n` ; 
380+   if  ( table )  report  +=  addTableLine ( prefix ,  tableWidth ) ; 
272381
382+   // Body 
273383  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` ; 
384+     const  file  =  summary . files [ i ] ; 
385+     const  relativePath  =  relative ( summary . workingDirectory ,  file . path ) ; 
386+ 
387+     let  fileCoverage  =  0 ; 
388+     const  coverages  =  ArrayPrototypeMap ( kColumnsKeys ,  ( columnKey )  =>  { 
389+       const  percent  =  file [ columnKey ] ; 
390+       fileCoverage  +=  percent ; 
391+       return  percent ; 
392+     } ) ; 
393+     fileCoverage  /=  kColumnsKeys . length ; 
394+ 
395+     report  +=  `${ prefix } ${ getCell ( relativePath ,  filePadLength ,  StringPrototypePadEnd ,  truncateStart ,  fileCoverage ) } ${ kSeparator }  `  + 
396+               `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( coverages ,  ( coverage ,  j )  =>  getCell ( NumberPrototypeToFixed ( coverage ,  2 ) ,  columnPadLengths [ j ] ,  StringPrototypePadStart ,  false ,  coverage ) ) ,  kSeparator ) } ${ kSeparator }  `  + 
397+               `${ getCell ( formatUncoveredLines ( file . uncoveredLineNumbers ,  table ) ,  uncoveredLinesPadLength ,  false ,  truncateEnd ) }  \n` ; 
289398  } 
290399
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` ; 
400+   // Foot 
401+   if   ( table )   report  +=  addTableLine ( prefix ,   tableWidth ) ; 
402+   report   +=   `${ prefix } ${ getCell ( 'all files' ,   filePadLength ,   StringPrototypePadEnd ,   truncateEnd ) } ${ kSeparator }  `  + 
403+             `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( kColumnsKeys ,   ( columnKey ,   j )   =>   getCell ( NumberPrototypeToFixed ( summary . totals [ columnKey ] ,   2 ) ,   columnPadLengths [ j ] ,   StringPrototypePadStart ,   false ,   summary . totals [ columnKey ] ) ) ,   kSeparator ) }   |\n`  ; 
404+   if   ( table )   report   +=   addTableLine ( prefix ,   tableWidth ) ; 
296405
297-   report  +=  `${ pad } ${ symbol }  end of coverage report\n` ; 
406+   report  +=  `${ prefix }  end of coverage report\n` ; 
298407  if  ( color )  { 
299408    report  +=  white ; 
300409  } 
0 commit comments