9393use function explode ;
9494use function file_get_contents ;
9595use function htmlspecialchars ;
96+ use function implode ;
9697use function is_string ;
9798use function ksort ;
99+ use function max ;
100+ use function min ;
98101use function range ;
99- use function sort ;
100102use function sprintf ;
101103use function str_ends_with ;
102104use function str_replace ;
@@ -812,100 +814,75 @@ private function renderBranchStructure(FileNode $node): string
812814
813815 $ coverageData = $ node ->functionCoverageData ();
814816 $ testData = $ node ->testData ();
815- $ codeLines = $ this ->loadFile ($ node ->pathAsString ());
816817 $ branches = '' ;
817818
818819 ksort ($ coverageData );
819820
820821 /** @var ProcessedFunctionCoverageData $methodData */
821822 foreach ($ coverageData as $ methodName => $ methodData ) {
822- $ branchStructure = '' ;
823-
824- /** @var ProcessedBranchCoverageData $branch */
825- foreach ($ methodData ->branches as $ branch ) {
826- $ branchStructure .= $ this ->renderBranchLines ($ branch , $ codeLines , $ testData );
827- }
828-
829- if ($ branchStructure !== '' ) { // don't show empty branches
830- $ branches .= '<h5 class="structure-heading"><a name=" ' . htmlspecialchars ($ methodName , self ::HTML_SPECIAL_CHARS_FLAGS ) . '"> ' . $ this ->abbreviateMethodName ($ methodName ) . '</a></h5> ' . "\n" ;
831- $ branches .= $ branchStructure ;
832- }
833- }
834-
835- $ branchesTemplate ->setVar (['branches ' => $ branches ]);
836-
837- return $ branchesTemplate ->render ();
838- }
839-
840- /**
841- * @param list<string> $codeLines
842- * @param array<string, TestType> $testData
843- */
844- private function renderBranchLines (ProcessedBranchCoverageData $ branch , array $ codeLines , array $ testData ): string
845- {
846- $ linesTemplate = new Template ($ this ->templatePath . 'lines.html.dist ' , '{{ ' , '}} ' );
847- $ singleLineTemplate = new Template ($ this ->templatePath . 'line.html.dist ' , '{{ ' , '}} ' );
848-
849- $ lines = '' ;
850-
851- $ branchLines = range ($ branch ->line_start , $ branch ->line_end );
852- sort ($ branchLines ); // sometimes end_line < start_line
853-
854- /** @var int $line */
855- foreach ($ branchLines as $ line ) {
856- if (!isset ($ codeLines [$ line ])) { // blank line at end of file is sometimes included here
823+ if ($ methodData ->branches === []) {
857824 continue ;
858825 }
859826
860- $ popoverContent = '' ;
861- $ popoverTitle = '' ;
827+ $ branches .= '<h5 class="structure-heading"><a name=" ' . htmlspecialchars ($ methodName , self ::HTML_SPECIAL_CHARS_FLAGS ) . '"> ' . $ this ->abbreviateMethodName ($ methodName ) . '</a></h5> ' . "\n" ;
828+ $ branches .= '<table class="table table-bordered table-sm structure-table"> ' . "\n" ;
829+ $ branches .= '<thead><tr><th>#</th><th>Lines</th><th>Status</th><th>Tests</th></tr></thead> ' . "\n" ;
830+ $ branches .= '<tbody> ' . "\n" ;
862831
863- $ numTests = count ( $ branch -> hit ) ;
832+ $ branchIndex = 1 ;
864833
865- if ($ numTests === 0 ) {
866- $ trClass = 'danger ' ;
867- } else {
868- $ lineCss = 'covered-by-large-tests ' ;
869- $ popoverContent = '<ul> ' ;
834+ /** @var ProcessedBranchCoverageData $branch */
835+ foreach ($ methodData ->branches as $ branch ) {
836+ $ lineStart = min ($ branch ->line_start , $ branch ->line_end );
837+ $ lineEnd = max ($ branch ->line_start , $ branch ->line_end );
838+ $ linesLabel = $ lineStart === $ lineEnd
839+ ? sprintf ('<a href="#%d">L%d</a> ' , $ lineStart , $ lineStart )
840+ : sprintf ('<a href="#%d">L%d</a>–<a href="#%d">L%d</a> ' , $ lineStart , $ lineStart , $ lineEnd , $ lineEnd );
870841
871- if ($ numTests > 1 ) {
872- $ popoverTitle = $ numTests . ' tests cover this branch ' ;
842+ $ numTests = count ($ branch ->hit );
843+
844+ if ($ numTests === 0 ) {
845+ $ statusClass = 'danger ' ;
846+ $ statusLabel = 'Not covered ' ;
847+ $ testsLabel = '— ' ;
873848 } else {
874- $ popoverTitle = '1 test covers this branch ' ;
875- }
849+ $ statusClass = 'success ' ;
850+ $ statusLabel = 'Covered ' ;
851+
852+ $ popoverContent = '<ul> ' ;
876853
877- foreach ($ branch ->hit as $ test ) {
878- if ($ lineCss === 'covered-by-large-tests ' && $ testData [$ test ]['size ' ] === 'medium ' ) {
879- $ lineCss = 'covered-by-medium-tests ' ;
880- } elseif ($ testData [$ test ]['size ' ] === 'small ' ) {
881- $ lineCss = 'covered-by-small-tests ' ;
854+ foreach ($ branch ->hit as $ test ) {
855+ $ popoverContent .= $ this ->createPopoverContentForTest ($ test , $ testData [$ test ]);
882856 }
883857
884- $ popoverContent .= $ this ->createPopoverContentForTest ($ test , $ testData [$ test ]);
885- }
886- $ trClass = $ lineCss . ' popin ' ;
887- }
858+ $ popoverContent .= '</ul> ' ;
888859
889- $ popover = '' ;
860+ $ testsLabel = sprintf (
861+ '<span class="popin" data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true">%s</span> ' ,
862+ $ numTests === 1 ? '1 test ' : $ numTests . ' tests ' ,
863+ htmlspecialchars ($ popoverContent , self ::HTML_SPECIAL_CHARS_FLAGS ),
864+ $ numTests === 1 ? '1 test ' : $ numTests . ' tests ' ,
865+ );
866+ }
890867
891- if ($ popoverTitle !== '' ) {
892- $ popover = sprintf (
893- ' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true" ' ,
894- $ popoverTitle ,
895- htmlspecialchars ($ popoverContent , self ::HTML_SPECIAL_CHARS_FLAGS ),
868+ $ branches .= sprintf (
869+ '<tr class="%s"><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr> ' . "\n" ,
870+ $ statusClass ,
871+ $ branchIndex ,
872+ $ linesLabel ,
873+ $ statusLabel ,
874+ $ testsLabel ,
896875 );
897- }
898876
899- $ lines .= $ this -> renderLine ( $ singleLineTemplate , $ line , $ codeLines [ $ line - 1 ], $ trClass , $ popover ) ;
900- }
877+ $ branchIndex ++ ;
878+ }
901879
902- if ($ lines === '' ) {
903- return '' ;
880+ $ branches .= '</tbody></table> ' . "\n" ;
904881 }
905882
906- $ linesTemplate ->setVar (['lines ' => $ lines ]);
883+ $ branchesTemplate ->setVar (['branches ' => $ branches ]);
907884
908- return $ linesTemplate ->render ();
885+ return $ branchesTemplate ->render ();
909886 }
910887
911888 private function renderPathStructure (FileNode $ node ): string
@@ -914,116 +891,86 @@ private function renderPathStructure(FileNode $node): string
914891
915892 $ coverageData = $ node ->functionCoverageData ();
916893 $ testData = $ node ->testData ();
917- $ codeLines = $ this ->loadFile ($ node ->pathAsString ());
918894 $ paths = '' ;
919895
920896 ksort ($ coverageData );
921897
922898 /** @var ProcessedFunctionCoverageData $methodData */
923899 foreach ($ coverageData as $ methodName => $ methodData ) {
924- $ pathStructure = '' ;
925-
926- if (count ($ methodData ->paths ) > 100 ) {
927- $ pathStructure .= '<p> ' . count ($ methodData ->paths ) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.</p> ' ;
928-
900+ if ($ methodData ->paths === []) {
929901 continue ;
930902 }
931903
932- foreach ($ methodData ->paths as $ path ) {
933- $ pathStructure .= $ this ->renderPathLines ($ path , $ methodData ->branches , $ codeLines , $ testData );
934- }
935-
936- if ($ pathStructure !== '' ) {
904+ if (count ($ methodData ->paths ) > 100 ) {
937905 $ paths .= '<h5 class="structure-heading"><a name=" ' . htmlspecialchars ($ methodName , self ::HTML_SPECIAL_CHARS_FLAGS ) . '"> ' . $ this ->abbreviateMethodName ($ methodName ) . '</a></h5> ' . "\n" ;
938- $ paths .= $ pathStructure ;
939- }
940- }
906+ $ paths .= '<p> ' . count ($ methodData ->paths ) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.</p> ' ;
941907
942- $ pathsTemplate ->setVar (['paths ' => $ paths ]);
908+ continue ;
909+ }
943910
944- return $ pathsTemplate ->render ();
945- }
911+ $ paths .= '<h5 class="structure-heading"><a name=" ' . htmlspecialchars ($ methodName , self ::HTML_SPECIAL_CHARS_FLAGS ) . '"> ' . $ this ->abbreviateMethodName ($ methodName ) . '</a></h5> ' . "\n" ;
912+ $ paths .= '<table class="table table-bordered table-sm structure-table"> ' . "\n" ;
913+ $ paths .= '<thead><tr><th>#</th><th>Branches</th><th>Status</th><th>Tests</th></tr></thead> ' . "\n" ;
914+ $ paths .= '<tbody> ' . "\n" ;
946915
947- /**
948- * @param array<int, ProcessedBranchCoverageData> $branches
949- * @param list<string> $codeLines
950- * @param array<string, TestType> $testData
951- */
952- private function renderPathLines (ProcessedPathCoverageData $ path , array $ branches , array $ codeLines , array $ testData ): string
953- {
954- $ linesTemplate = new Template ($ this ->templatePath . 'lines.html.dist ' , '{{ ' , '}} ' );
955- $ singleLineTemplate = new Template ($ this ->templatePath . 'line.html.dist ' , '{{ ' , '}} ' );
956-
957- $ lines = '' ;
958- $ first = true ;
916+ $ pathIndex = 1 ;
959917
960- foreach ($ path ->path as $ branchId ) {
961- if ($ first ) {
962- $ first = false ;
963- } else {
964- $ lines .= ' <tr><td colspan="2"> </td></tr> ' . "\n" ;
965- }
918+ foreach ($ methodData ->paths as $ path ) {
919+ $ branchLabels = [];
966920
967- $ branchLines = range ($ branches [$ branchId ]->line_start , $ branches [$ branchId ]->line_end );
968- sort ($ branchLines ); // sometimes end_line < start_line
921+ foreach ($ path ->path as $ branchId ) {
922+ $ branch = $ methodData ->branches [$ branchId ];
923+ $ branchLine = min ($ branch ->line_start , $ branch ->line_end );
969924
970- /** @var int $line */
971- foreach ($ branchLines as $ line ) {
972- if (!isset ($ codeLines [$ line ])) { // blank line at end of file is sometimes included here
973- continue ;
925+ $ branchLabels [] = sprintf ('<a href="#%d">L%d</a> ' , $ branchLine , $ branchLine );
974926 }
975927
976- $ popoverContent = '' ;
977- $ popoverTitle = '' ;
928+ $ branchesLabel = implode (' → ' , $ branchLabels );
978929
979930 $ numTests = count ($ path ->hit );
980931
981932 if ($ numTests === 0 ) {
982- $ trClass = 'danger ' ;
933+ $ statusClass = 'danger ' ;
934+ $ statusLabel = 'Not covered ' ;
935+ $ testsLabel = '— ' ;
983936 } else {
984- $ lineCss = 'covered-by-large-tests ' ;
985- $ popoverContent = '<ul> ' ;
937+ $ statusClass = 'success ' ;
938+ $ statusLabel = 'Covered ' ;
986939
987- if ($ numTests > 1 ) {
988- $ popoverTitle = $ numTests . ' tests cover this path ' ;
989- } else {
990- $ popoverTitle = '1 test covers this path ' ;
991- }
940+ $ popoverContent = '<ul> ' ;
992941
993942 foreach ($ path ->hit as $ test ) {
994- if ($ lineCss === 'covered-by-large-tests ' && $ testData [$ test ]['size ' ] === 'medium ' ) {
995- $ lineCss = 'covered-by-medium-tests ' ;
996- } elseif ($ testData [$ test ]['size ' ] === 'small ' ) {
997- $ lineCss = 'covered-by-small-tests ' ;
998- }
999-
1000943 $ popoverContent .= $ this ->createPopoverContentForTest ($ test , $ testData [$ test ]);
1001944 }
1002945
1003- $ trClass = $ lineCss . ' popin ' ;
1004- }
1005-
1006- $ popover = '' ;
946+ $ popoverContent .= '</ul> ' ;
1007947
1008- if ($ popoverTitle !== '' ) {
1009- $ popover = sprintf (
1010- ' data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true" ' ,
1011- $ popoverTitle ,
948+ $ testsLabel = sprintf (
949+ '<span class="popin" data-bs-title="%s" data-bs-content="%s" data-bs-placement="top" data-bs-html="true">%s</span> ' ,
950+ $ numTests === 1 ? '1 test ' : $ numTests . ' tests ' ,
1012951 htmlspecialchars ($ popoverContent , self ::HTML_SPECIAL_CHARS_FLAGS ),
952+ $ numTests === 1 ? '1 test ' : $ numTests . ' tests ' ,
1013953 );
1014954 }
1015955
1016- $ lines .= $ this ->renderLine ($ singleLineTemplate , $ line , $ codeLines [$ line - 1 ], $ trClass , $ popover );
956+ $ paths .= sprintf (
957+ '<tr class="%s"><td>%d</td><td>%s</td><td>%s</td><td>%s</td></tr> ' . "\n" ,
958+ $ statusClass ,
959+ $ pathIndex ,
960+ $ branchesLabel ,
961+ $ statusLabel ,
962+ $ testsLabel ,
963+ );
964+
965+ $ pathIndex ++;
1017966 }
1018- }
1019967
1020- if ($ lines === '' ) {
1021- return '' ;
968+ $ paths .= '</tbody></table> ' . "\n" ;
1022969 }
1023970
1024- $ linesTemplate ->setVar (['lines ' => $ lines ]);
971+ $ pathsTemplate ->setVar (['paths ' => $ paths ]);
1025972
1026- return $ linesTemplate ->render ();
973+ return $ pathsTemplate ->render ();
1027974 }
1028975
1029976 private function renderLine (Template $ template , int $ lineNumber , string $ lineContent , string $ class , string $ popover ): string
0 commit comments