11'use strict' ;
22const {
33 ArrayPrototypeFlatMap,
4+ ArrayPrototypeForEach,
45 ArrayPrototypeJoin,
56 ArrayPrototypeMap,
7+ ArrayPrototypePop,
68 ArrayPrototypePush,
79 ArrayPrototypeReduce,
810 ArrayPrototypeSome,
@@ -24,7 +26,7 @@ const {
2426} = primordials ;
2527
2628const { AsyncResource } = require ( 'async_hooks' ) ;
27- const { relative } = require ( 'path' ) ;
29+ const { relative, sep } = require ( 'path' ) ;
2830const { createWriteStream } = require ( 'fs' ) ;
2931const { pathToFileURL } = require ( 'internal/url' ) ;
3032const { createDeferredPromise } = require ( 'internal/util' ) ;
@@ -409,6 +411,36 @@ const kColumns = ['line %', 'branch %', 'funcs %'];
409411const kColumnsKeys = [ 'coveredLinePercent' , 'coveredBranchPercent' , 'coveredFunctionPercent' ] ;
410412const kSeparator = ' | ' ;
411413
414+ function buildFileTree ( summary ) {
415+ const tree = { __proto__ : null } ;
416+ let treeDepth = 1 ;
417+ let longestFile = 0 ;
418+
419+ ArrayPrototypeForEach ( summary . files , ( file ) => {
420+ let longestPart = 0 ;
421+ const parts = StringPrototypeSplit ( relative ( summary . workingDirectory , file . path ) , sep ) ;
422+ let current = tree ;
423+
424+ ArrayPrototypeForEach ( parts , ( part , index ) => {
425+ if ( ! current [ part ] ) {
426+ current [ part ] = { __proto__ : null } ;
427+ }
428+ current = current [ part ] ;
429+ // If this is the last part, add the file to the tree
430+ if ( index === parts . length - 1 ) {
431+ current . file = file ;
432+ }
433+ // Keep track of the longest part for padding
434+ longestPart = MathMax ( longestPart , part . length ) ;
435+ } ) ;
436+
437+ treeDepth = MathMax ( treeDepth , parts . length ) ;
438+ longestFile = MathMax ( longestPart , longestFile ) ;
439+ } ) ;
440+
441+ return { __proto__ : null , tree, treeDepth, longestFile } ;
442+ }
443+
412444function getCoverageReport ( pad , summary , symbol , color , table ) {
413445 const prefix = `${ pad } ${ symbol } ` ;
414446 let report = `${ color } ${ prefix } start of coverage report\n` ;
@@ -418,11 +450,19 @@ function getCoverageReport(pad, summary, symbol, color, table) {
418450 let uncoveredLinesPadLength ;
419451 let tableWidth ;
420452
453+ // Create a tree of file paths
454+ const { tree, treeDepth, longestFile } = buildFileTree ( summary ) ;
421455 if ( table ) {
422- // Get expected column sizes
423- filePadLength = table && ArrayPrototypeReduce ( summary . files , ( acc , file ) =>
424- MathMax ( acc , relative ( summary . workingDirectory , file . path ) . length ) , 0 ) ;
456+ // Calculate expected column sizes based on the tree
457+ filePadLength = table && longestFile ;
458+ filePadLength += ( treeDepth - 1 ) ;
459+ if ( color ) {
460+ filePadLength += 2 ;
461+ }
425462 filePadLength = MathMax ( filePadLength , 'file' . length ) ;
463+ if ( filePadLength > ( process . stdout . columns / 2 ) ) {
464+ filePadLength = MathFloor ( process . stdout . columns / 2 ) ;
465+ }
426466 const fileWidth = filePadLength + 2 ;
427467
428468 columnPadLengths = ArrayPrototypeMap ( kColumns , ( column ) => ( table ? MathMax ( column . length , 6 ) : 0 ) ) ;
@@ -435,26 +475,17 @@ function getCoverageReport(pad, summary, symbol, color, table) {
435475
436476 tableWidth = fileWidth + columnsWidth + uncoveredLinesWidth ;
437477
438- // Fit with sensible defaults
439478 const availableWidth = ( process . stdout . columns || Infinity ) - prefix . length ;
440479 const columnsExtras = tableWidth - availableWidth ;
441480 if ( table && columnsExtras > 0 ) {
442- // Ensure file name is sufficiently visible
443- const minFilePad = MathMin ( 8 , filePadLength ) ;
444- filePadLength -= MathFloor ( columnsExtras * 0.2 ) ;
445- filePadLength = MathMax ( filePadLength , minFilePad ) ;
446-
447- // Get rest of available space, subtracting margins
481+ filePadLength = MathMin ( availableWidth * 0.5 , filePadLength ) ;
448482 uncoveredLinesPadLength = MathMax ( availableWidth - columnsWidth - ( filePadLength + 2 ) - 2 , 1 ) ;
449-
450- // Update table width
451483 tableWidth = availableWidth ;
452484 } else {
453485 uncoveredLinesPadLength = Infinity ;
454486 }
455487 }
456488
457-
458489 function getCell ( string , width , pad , truncate , coverage ) {
459490 if ( ! table ) return string ;
460491
@@ -469,35 +500,85 @@ function getCoverageReport(pad, summary, symbol, color, table) {
469500 return result ;
470501 }
471502
472- // Head
473- if ( table ) report += addTableLine ( prefix , tableWidth ) ;
474- report += ` ${ prefix } ${ getCell ( 'file' , filePadLength , StringPrototypePadEnd , truncateEnd ) } ${ kSeparator } ` +
475- ` ${ ArrayPrototypeJoin ( ArrayPrototypeMap ( kColumns , ( column , i ) => getCell ( column , columnPadLengths [ i ] , StringPrototypePadStart ) ) , kSeparator ) } ${ kSeparator } ` +
476- ` ${ getCell ( 'uncovered lines' , uncoveredLinesPadLength , false , truncateEnd ) } \n` ;
477- if ( table ) report += addTableLine ( prefix , tableWidth ) ;
503+ function writeReportLine ( { file , depth = 0 , coveragesColumns , fileCoverage , uncoveredLines } ) {
504+ const fileColumn = ` ${ prefix } ${ StringPrototypeRepeat ( ' ' , depth ) } ${ getCell ( file , filePadLength - depth , StringPrototypePadEnd , truncateStart , fileCoverage ) } ` ;
505+ const coverageColumns = ArrayPrototypeJoin ( ArrayPrototypeMap ( coveragesColumns , ( coverage , j ) => {
506+ const coverageText = typeof coverage === 'number' ? NumberPrototypeToFixed ( coverage , 2 ) : coverage ;
507+ return getCell ( coverageText , columnPadLengths [ j ] , StringPrototypePadStart , false , coverage ) ;
508+ } ) , kSeparator ) ;
478509
479- // Body
480- for ( let i = 0 ; i < summary . files . length ; ++ i ) {
481- const file = summary . files [ i ] ;
482- const relativePath = relative ( summary . workingDirectory , file . path ) ;
510+ const uncoveredLinesColumn = getCell ( uncoveredLines , uncoveredLinesPadLength , false , truncateEnd ) ;
483511
484- let fileCoverage = 0 ;
485- const coverages = ArrayPrototypeMap ( kColumnsKeys , ( columnKey ) => {
486- const percent = file [ columnKey ] ;
487- fileCoverage += percent ;
488- return percent ;
489- } ) ;
490- fileCoverage /= kColumnsKeys . length ;
512+ return `${ fileColumn } ${ kSeparator } ${ coverageColumns } ${ kSeparator } ${ uncoveredLinesColumn } \n` ;
513+ }
491514
492- report += `${ prefix } ${ getCell ( relativePath , filePadLength , StringPrototypePadEnd , truncateStart , fileCoverage ) } ${ kSeparator } ` +
493- `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( coverages , ( coverage , j ) => getCell ( NumberPrototypeToFixed ( coverage , 2 ) , columnPadLengths [ j ] , StringPrototypePadStart , false , coverage ) ) , kSeparator ) } ${ kSeparator } ` +
494- `${ getCell ( formatUncoveredLines ( getUncoveredLines ( file . lines ) , table ) , uncoveredLinesPadLength , false , truncateEnd ) } \n` ;
515+ function printCoverageBodyTree ( tree , depth = 0 ) {
516+ for ( const key in tree ) {
517+ if ( tree [ key ] . file ) {
518+ const file = tree [ key ] . file ;
519+ const fileName = ArrayPrototypePop ( StringPrototypeSplit ( file . path , sep ) ) ;
520+
521+ let fileCoverage = 0 ;
522+ const coverages = ArrayPrototypeMap ( kColumnsKeys , ( columnKey ) => {
523+ const percent = file [ columnKey ] ;
524+ fileCoverage += percent ;
525+ return percent ;
526+ } ) ;
527+ fileCoverage /= kColumnsKeys . length ;
528+
529+ const uncoveredLines = formatUncoveredLines ( getUncoveredLines ( file . lines ) , table ) ;
530+
531+ report += writeReportLine ( {
532+ __proto__ : null ,
533+ file : fileName ,
534+ depth : depth ,
535+ coveragesColumns : coverages ,
536+ fileCoverage : fileCoverage ,
537+ uncoveredLines : uncoveredLines ,
538+ } ) ;
539+ } else {
540+ report += writeReportLine ( {
541+ __proto__ : null ,
542+ file : key ,
543+ depth : depth ,
544+ coveragesColumns : ArrayPrototypeMap ( columnPadLengths , ( ) => '' ) ,
545+ fileCoverage : undefined ,
546+ uncoveredLines : '' ,
547+ } ) ;
548+ printCoverageBodyTree ( tree [ key ] , depth + 1 ) ;
549+ }
550+ }
495551 }
496552
497- // Foot
553+ // -------------------------- Coverage Report --------------------------
554+ if ( table ) report += addTableLine ( prefix , tableWidth ) ;
555+
556+ // Print the header
557+ report += writeReportLine ( {
558+ __proto__ : null ,
559+ file : 'file' ,
560+ coveragesColumns : kColumns ,
561+ fileCoverage : undefined ,
562+ uncoveredLines : 'uncovered lines' ,
563+ } ) ;
564+
565+ if ( table ) report += addTableLine ( prefix , tableWidth ) ;
566+
567+ // Print the body
568+ printCoverageBodyTree ( tree ) ;
569+
498570 if ( table ) report += addTableLine ( prefix , tableWidth ) ;
499- report += `${ prefix } ${ getCell ( 'all files' , filePadLength , StringPrototypePadEnd , truncateEnd ) } ${ kSeparator } ` +
500- `${ ArrayPrototypeJoin ( ArrayPrototypeMap ( kColumnsKeys , ( columnKey , j ) => getCell ( NumberPrototypeToFixed ( summary . totals [ columnKey ] , 2 ) , columnPadLengths [ j ] , StringPrototypePadStart , false , summary . totals [ columnKey ] ) ) , kSeparator ) } |\n` ;
571+
572+ // Print the footer
573+ const allFilesCoverages = ArrayPrototypeMap ( kColumnsKeys , ( columnKey ) => summary . totals [ columnKey ] ) ;
574+ report += writeReportLine ( {
575+ __proto__ : null ,
576+ file : 'all files' ,
577+ coveragesColumns : allFilesCoverages ,
578+ fileCoverage : undefined ,
579+ uncoveredLines : '' ,
580+ } ) ;
581+
501582 if ( table ) report += addTableLine ( prefix , tableWidth ) ;
502583
503584 report += `${ prefix } end of coverage report\n` ;
0 commit comments