|
414 | 414 | var treeContainer = document.getElementById('file-tree'); |
415 | 415 | if (!treeContainer) return; |
416 | 416 |
|
417 | | - renderTree(treeContainer, window.GCOVR_TREE_DATA); |
| 417 | + // Tree data is produced already-normalized and already-sorted by the |
| 418 | + // upstream tooling (Python's gcovr_build_tree.py or gcovr itself), so |
| 419 | + // no normalize/sort pass is needed here. We just join single-child |
| 420 | + // dir chains for sidebar compactness before rendering. |
| 421 | + joinSingleChildDirs(window.GCOVR_TREE_DATA); |
| 422 | + sortTree(window.GCOVR_TREE_DATA); |
| 423 | + renderTree(treeContainer, window.GCOVR_TREE_DATA); |
| 424 | + } |
| 425 | + |
| 426 | + // Re-sort the tree: directories first, then files, alphabetically within |
| 427 | + // each group. Python already sorts each level, but normalizeTree creates |
| 428 | + // synthetic directory nodes from multi-segment FILE entries (e.g. a deep |
| 429 | + // chain like subdir1/subdir2/subdir3/file.hpp that gcovr itself collapsed). |
| 430 | + // Those synthetic dirs end up wherever the originating file landed in the |
| 431 | + // Python sort — i.e. in the file bucket — so without this pass they appear |
| 432 | + // mixed in with the files instead of at the top with the other directories. |
| 433 | + function sortTree(nodes) { |
| 434 | + if (!nodes || nodes.length === 0) return; |
| 435 | + nodes.sort(function(a, b) { |
| 436 | + var aIsDir = a.isDirectory || (a.children && a.children.length > 0); |
| 437 | + var bIsDir = b.isDirectory || (b.children && b.children.length > 0); |
| 438 | + if (aIsDir && !bIsDir) return -1; |
| 439 | + if (!aIsDir && bIsDir) return 1; |
| 440 | + var aName = (a.name || '').toLowerCase(); |
| 441 | + var bName = (b.name || '').toLowerCase(); |
| 442 | + return aName.localeCompare(bName); |
| 443 | + }); |
| 444 | + for (var i = 0; i < nodes.length; i++) { |
| 445 | + if (nodes[i].children) sortTree(nodes[i].children); |
| 446 | + } |
| 447 | + } |
| 448 | + |
| 449 | + // Join chains of single-child directories into one sidebar entry. |
| 450 | + // If a directory contains nothing but one child directory, the two are |
| 451 | + // merged: the name becomes "parent/child", and the link + stats are taken |
| 452 | + // from the (deepest) child. The grandchildren become the new children. |
| 453 | + // Repeats until the chain ends (multiple children, or a file appears). |
| 454 | + // Result: e.g. include > boost > url > [files] becomes include/boost/url |
| 455 | + // as a single entry whose click navigates straight to the url directory. |
| 456 | + function joinSingleChildDirs(nodes) { |
| 457 | + if (!nodes) return; |
| 458 | + for (var i = 0; i < nodes.length; i++) { |
| 459 | + var node = nodes[i]; |
| 460 | + if (!node.isDirectory || !node.children) continue; |
| 461 | + while (node.children.length === 1 && node.children[0].isDirectory) { |
| 462 | + var child = node.children[0]; |
| 463 | + node.name = node.name + '/' + child.name; |
| 464 | + // The joined entry represents the deepest directory for clicks |
| 465 | + // and for the coverage stats shown next to it. Statically-named |
| 466 | + // assignments avoid the dynamic [key] indexing that static |
| 467 | + // analyzers flag as a prototype-pollution risk. |
| 468 | + if (child.link) node.link = child.link; |
| 469 | + if (child.coverage) node.coverage = child.coverage; |
| 470 | + if (child.coverageClass) node.coverageClass = child.coverageClass; |
| 471 | + if (child.linesTotal) node.linesTotal = child.linesTotal; |
| 472 | + if (child.linesExec) node.linesExec = child.linesExec; |
| 473 | + if (child.linesCoverage) node.linesCoverage = child.linesCoverage; |
| 474 | + if (child.linesClass) node.linesClass = child.linesClass; |
| 475 | + if (child.functionsCoverage) node.functionsCoverage = child.functionsCoverage; |
| 476 | + if (child.functionsClass) node.functionsClass = child.functionsClass; |
| 477 | + if (child.branchesCoverage) node.branchesCoverage = child.branchesCoverage; |
| 478 | + if (child.branchesClass) node.branchesClass = child.branchesClass; |
| 479 | + node.children = child.children || []; |
| 480 | + } |
| 481 | + joinSingleChildDirs(node.children); |
| 482 | + } |
418 | 483 | } |
419 | 484 |
|
420 | 485 | // Save expanded folder paths to localStorage |
|
1098 | 1163 |
|
1099 | 1164 | var aVal = a.dataset[key] || a.querySelector('[data-sort]')?.dataset.sort || ''; |
1100 | 1165 | var bVal = b.dataset[key] || b.querySelector('[data-sort]')?.dataset.sort || ''; |
1101 | | - if (key == 'filename' && localStorage.getItem('gcovr-view-mode') === 'nested') { |
1102 | | - aVal = aVal.split('/').pop(); |
1103 | | - bVal = bVal.split('/').pop(); |
1104 | | - } |
1105 | 1166 |
|
1106 | 1167 | // Try to parse as numbers |
1107 | 1168 | var aNum = parseFloat(aVal); |
|
0 commit comments