1
1
'use strict' ;
2
2
const {
3
3
ArrayPrototypeFlatMap,
4
+ ArrayPrototypeForEach,
4
5
ArrayPrototypeJoin,
5
6
ArrayPrototypeMap,
7
+ ArrayPrototypePop,
6
8
ArrayPrototypePush,
7
9
ArrayPrototypeReduce,
8
10
ArrayPrototypeSome,
@@ -24,7 +26,7 @@ const {
24
26
} = primordials ;
25
27
26
28
const { AsyncResource } = require ( 'async_hooks' ) ;
27
- const { relative } = require ( 'path' ) ;
29
+ const { relative, sep } = require ( 'path' ) ;
28
30
const { createWriteStream } = require ( 'fs' ) ;
29
31
const { pathToFileURL } = require ( 'internal/url' ) ;
30
32
const { createDeferredPromise } = require ( 'internal/util' ) ;
@@ -409,6 +411,36 @@ const kColumns = ['line %', 'branch %', 'funcs %'];
409
411
const kColumnsKeys = [ 'coveredLinePercent' , 'coveredBranchPercent' , 'coveredFunctionPercent' ] ;
410
412
const kSeparator = ' | ' ;
411
413
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
+
412
444
function getCoverageReport ( pad , summary , symbol , color , table ) {
413
445
const prefix = `${ pad } ${ symbol } ` ;
414
446
let report = `${ color } ${ prefix } start of coverage report\n` ;
@@ -418,11 +450,19 @@ function getCoverageReport(pad, summary, symbol, color, table) {
418
450
let uncoveredLinesPadLength ;
419
451
let tableWidth ;
420
452
453
+ // Create a tree of file paths
454
+ const { tree, treeDepth, longestFile } = buildFileTree ( summary ) ;
421
455
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
+ }
425
462
filePadLength = MathMax ( filePadLength , 'file' . length ) ;
463
+ if ( filePadLength > ( process . stdout . columns / 2 ) ) {
464
+ filePadLength = MathFloor ( process . stdout . columns / 2 ) ;
465
+ }
426
466
const fileWidth = filePadLength + 2 ;
427
467
428
468
columnPadLengths = ArrayPrototypeMap ( kColumns , ( column ) => ( table ? MathMax ( column . length , 6 ) : 0 ) ) ;
@@ -435,26 +475,17 @@ function getCoverageReport(pad, summary, symbol, color, table) {
435
475
436
476
tableWidth = fileWidth + columnsWidth + uncoveredLinesWidth ;
437
477
438
- // Fit with sensible defaults
439
478
const availableWidth = ( process . stdout . columns || Infinity ) - prefix . length ;
440
479
const columnsExtras = tableWidth - availableWidth ;
441
480
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 ) ;
448
482
uncoveredLinesPadLength = MathMax ( availableWidth - columnsWidth - ( filePadLength + 2 ) - 2 , 1 ) ;
449
-
450
- // Update table width
451
483
tableWidth = availableWidth ;
452
484
} else {
453
485
uncoveredLinesPadLength = Infinity ;
454
486
}
455
487
}
456
488
457
-
458
489
function getCell ( string , width , pad , truncate , coverage ) {
459
490
if ( ! table ) return string ;
460
491
@@ -469,35 +500,85 @@ function getCoverageReport(pad, summary, symbol, color, table) {
469
500
return result ;
470
501
}
471
502
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 ) ;
478
509
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 ) ;
483
511
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
+ }
491
514
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
+ }
495
551
}
496
552
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
+
498
570
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
+
501
582
if ( table ) report += addTableLine ( prefix , tableWidth ) ;
502
583
503
584
report += `${ prefix } end of coverage report\n` ;
0 commit comments