simplecov 0.22.0 → 1.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -1
  3. data/LICENSE +1 -1
  4. data/README.md +1009 -511
  5. data/doc/alternate-formatters.md +0 -5
  6. data/doc/commercial-services.md +5 -5
  7. data/exe/simplecov +11 -0
  8. data/lib/minitest/simplecov_plugin.rb +13 -5
  9. data/lib/simplecov/autostart.rb +11 -0
  10. data/lib/simplecov/cli/clean.rb +47 -0
  11. data/lib/simplecov/cli/coverage.rb +91 -0
  12. data/lib/simplecov/cli/diff.rb +151 -0
  13. data/lib/simplecov/cli/dotfile.rb +100 -0
  14. data/lib/simplecov/cli/merge.rb +116 -0
  15. data/lib/simplecov/cli/open.rb +50 -0
  16. data/lib/simplecov/cli/report.rb +84 -0
  17. data/lib/simplecov/cli/run.rb +36 -0
  18. data/lib/simplecov/cli/serve.rb +139 -0
  19. data/lib/simplecov/cli/uncovered.rb +107 -0
  20. data/lib/simplecov/cli.rb +150 -0
  21. data/lib/simplecov/color.rb +74 -0
  22. data/lib/simplecov/combine/branches_combiner.rb +3 -2
  23. data/lib/simplecov/combine/files_combiner.rb +7 -1
  24. data/lib/simplecov/combine/lines_combiner.rb +19 -17
  25. data/lib/simplecov/combine/methods_combiner.rb +26 -0
  26. data/lib/simplecov/combine/results_combiner.rb +5 -4
  27. data/lib/simplecov/command_guesser.rb +46 -32
  28. data/lib/simplecov/configuration/coverage.rb +171 -0
  29. data/lib/simplecov/configuration/coverage_criteria.rb +156 -0
  30. data/lib/simplecov/configuration/filters.rb +197 -0
  31. data/lib/simplecov/configuration/formatting.rb +119 -0
  32. data/lib/simplecov/configuration/ignored_entries.rb +63 -0
  33. data/lib/simplecov/configuration/merging.rb +74 -0
  34. data/lib/simplecov/configuration/thresholds.rb +174 -0
  35. data/lib/simplecov/configuration.rb +86 -407
  36. data/lib/simplecov/coverage_statistics.rb +12 -9
  37. data/lib/simplecov/coverage_violations.rb +148 -0
  38. data/lib/simplecov/defaults.rb +27 -20
  39. data/lib/simplecov/deprecation.rb +47 -0
  40. data/lib/simplecov/directive.rb +162 -0
  41. data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
  42. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +19 -57
  43. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
  44. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -27
  45. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
  46. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +38 -21
  47. data/lib/simplecov/exit_codes.rb +3 -0
  48. data/lib/simplecov/exit_handling.rb +158 -0
  49. data/lib/simplecov/file_list.rb +61 -17
  50. data/lib/simplecov/filter.rb +69 -24
  51. data/lib/simplecov/formatter/base.rb +101 -0
  52. data/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
  53. data/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
  54. data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
  55. data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
  56. data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
  57. data/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
  58. data/lib/simplecov/formatter/html_formatter.rb +79 -0
  59. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
  60. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +127 -0
  61. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  62. data/lib/simplecov/formatter/json_formatter.rb +77 -0
  63. data/lib/simplecov/formatter/multi_formatter.rb +4 -5
  64. data/lib/simplecov/formatter/simple_formatter.rb +9 -11
  65. data/lib/simplecov/formatter.rb +4 -0
  66. data/lib/simplecov/last_run.rb +10 -3
  67. data/lib/simplecov/lines_classifier.rb +26 -13
  68. data/lib/simplecov/load_global_config.rb +9 -4
  69. data/lib/simplecov/parallel_adapters/base.rb +51 -0
  70. data/lib/simplecov/parallel_adapters/generic.rb +42 -0
  71. data/lib/simplecov/parallel_adapters/parallel_tests.rb +77 -0
  72. data/lib/simplecov/parallel_adapters.rb +83 -0
  73. data/lib/simplecov/parallel_coordination.rb +95 -0
  74. data/lib/simplecov/process.rb +26 -14
  75. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  76. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  77. data/lib/simplecov/profiles/rails.rb +24 -10
  78. data/lib/simplecov/profiles/root_filter.rb +6 -5
  79. data/lib/simplecov/profiles/strict.rb +32 -0
  80. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  81. data/lib/simplecov/profiles.rb +32 -3
  82. data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  83. data/lib/simplecov/result/source_file_builder.rb +51 -0
  84. data/lib/simplecov/result.rb +97 -19
  85. data/lib/simplecov/result_adapter.rb +68 -6
  86. data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  87. data/lib/simplecov/result_merger/resultset_file.rb +38 -0
  88. data/lib/simplecov/result_merger/resultset_store.rb +50 -0
  89. data/lib/simplecov/result_merger.rb +54 -90
  90. data/lib/simplecov/result_processing.rb +162 -0
  91. data/lib/simplecov/simulate_coverage.rb +54 -8
  92. data/lib/simplecov/source_file/branch.rb +1 -3
  93. data/lib/simplecov/source_file/branch_builder.rb +114 -0
  94. data/lib/simplecov/source_file/builder_context.rb +28 -0
  95. data/lib/simplecov/source_file/line.rb +7 -2
  96. data/lib/simplecov/source_file/line_builder.rb +43 -0
  97. data/lib/simplecov/source_file/method.rb +52 -0
  98. data/lib/simplecov/source_file/method_builder.rb +58 -0
  99. data/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  100. data/lib/simplecov/source_file/skip_chunks.rb +77 -0
  101. data/lib/simplecov/source_file/source_loader.rb +63 -0
  102. data/lib/simplecov/source_file/statistics.rb +57 -0
  103. data/lib/simplecov/source_file.rb +66 -232
  104. data/lib/simplecov/static_coverage_extractor/visitor.rb +193 -0
  105. data/lib/simplecov/static_coverage_extractor.rb +111 -0
  106. data/lib/simplecov/useless_results_remover.rb +16 -7
  107. data/lib/simplecov/version.rb +1 -1
  108. data/lib/simplecov-html.rb +4 -0
  109. data/lib/simplecov.rb +148 -377
  110. data/lib/simplecov_json_formatter.rb +4 -0
  111. data/schemas/coverage-v1.0.schema.json +300 -0
  112. data/schemas/coverage.schema.json +300 -0
  113. metadata +89 -56
  114. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1 @@
1
+ *,*:before,*:after{box-sizing:border-box}*{margin:0;padding:0}html{-moz-text-size-adjust:none;-webkit-text-size-adjust:none;text-size-adjust:none}body{min-height:100vh;line-height:1.5;-webkit-font-smoothing:antialiased}img,picture,svg{display:block;max-width:100%}input,button,textarea,select{font:inherit}h1,h2,h3,h4,h5,h6{overflow-wrap:break-word;text-wrap:balance}p{overflow-wrap:break-word;text-wrap:pretty}table{border-collapse:collapse;border-spacing:0}ul,ol{list-style:none}a{text-decoration-skip-ink:auto;color:currentColor}.hide{display:none}.hljs{color:#24292e}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#005cc5}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-comment,.hljs-code,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media(prefers-color-scheme:dark){:root:not(.light-mode) .hljs{color:#c9d1d9}:root:not(.light-mode) .hljs-doctag,:root:not(.light-mode) .hljs-keyword,:root:not(.light-mode) .hljs-meta .hljs-keyword,:root:not(.light-mode) .hljs-template-tag,:root:not(.light-mode) .hljs-template-variable,:root:not(.light-mode) .hljs-type,:root:not(.light-mode) .hljs-variable.language_{color:#ff7b72}:root:not(.light-mode) .hljs-title,:root:not(.light-mode) .hljs-title.class_,:root:not(.light-mode) .hljs-title.class_.inherited__,:root:not(.light-mode) .hljs-title.function_{color:#d2a8ff}:root:not(.light-mode) .hljs-attr,:root:not(.light-mode) .hljs-attribute,:root:not(.light-mode) .hljs-literal,:root:not(.light-mode) .hljs-meta,:root:not(.light-mode) .hljs-number,:root:not(.light-mode) .hljs-operator,:root:not(.light-mode) .hljs-variable,:root:not(.light-mode) .hljs-selector-attr,:root:not(.light-mode) .hljs-selector-class,:root:not(.light-mode) .hljs-selector-id{color:#79c0ff}:root:not(.light-mode) .hljs-regexp,:root:not(.light-mode) .hljs-string,:root:not(.light-mode) .hljs-meta .hljs-string{color:#a5d6ff}:root:not(.light-mode) .hljs-built_in,:root:not(.light-mode) .hljs-symbol{color:#ffa657}:root:not(.light-mode) .hljs-comment,:root:not(.light-mode) .hljs-code,:root:not(.light-mode) .hljs-formula{color:#8b949e}:root:not(.light-mode) .hljs-name,:root:not(.light-mode) .hljs-quote,:root:not(.light-mode) .hljs-selector-tag,:root:not(.light-mode) .hljs-selector-pseudo{color:#7ee787}:root:not(.light-mode) .hljs-subst{color:#c9d1d9}:root:not(.light-mode) .hljs-section{color:#1f6feb}}.dark-mode .hljs{color:#c9d1d9}.dark-mode .hljs-doctag,.dark-mode .hljs-keyword,.dark-mode .hljs-meta .hljs-keyword,.dark-mode .hljs-template-tag,.dark-mode .hljs-template-variable,.dark-mode .hljs-type,.dark-mode .hljs-variable.language_{color:#ff7b72}.dark-mode .hljs-title,.dark-mode .hljs-title.class_,.dark-mode .hljs-title.class_.inherited__,.dark-mode .hljs-title.function_{color:#d2a8ff}.dark-mode .hljs-attr,.dark-mode .hljs-attribute,.dark-mode .hljs-literal,.dark-mode .hljs-meta,.dark-mode .hljs-number,.dark-mode .hljs-operator,.dark-mode .hljs-variable,.dark-mode .hljs-selector-attr,.dark-mode .hljs-selector-class,.dark-mode .hljs-selector-id{color:#79c0ff}.dark-mode .hljs-regexp,.dark-mode .hljs-string,.dark-mode .hljs-meta .hljs-string{color:#a5d6ff}.dark-mode .hljs-built_in,.dark-mode .hljs-symbol{color:#ffa657}.dark-mode .hljs-comment,.dark-mode .hljs-code,.dark-mode .hljs-formula{color:#8b949e}.dark-mode .hljs-name,.dark-mode .hljs-quote,.dark-mode .hljs-selector-tag,.dark-mode .hljs-selector-pseudo{color:#7ee787}.dark-mode .hljs-subst{color:#c9d1d9}.dark-mode .hljs-section{color:#1f6feb}:root{--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--font-mono: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace;--sp-1: 4px;--sp-2: 8px;--sp-3: 12px;--sp-4: 16px;--sp-5: 20px;--sp-6: 24px;--sp-8: 32px;--radius-sm: 8px;--radius-md: 12px;--radius-lg: 16px;--bg: #f0f1f3;--surface: #fff;--surface-alt: #f0f1f3;--text: #111;--text-secondary: #333;--text-tertiary: #444;--zebra: #f4f5f7;--border: #c0c5cc;--border-strong: #999;--accent: #0550ae;--accent-hover: #033d8b;--accent-subtle: #ddf4ff;--green: #116329;--red: #a40e26;--yellow: #7a5200;--orange: #953800;--covered-bg: #ccf5d0;--covered-line-num-bg: #9ae6a4;--missed-bg: #ffd8d5;--missed-line-num-bg: #ffb8b3;--never-bg: #fff;--never-line-num-bg: #f0f1f3;--skipped-bg: #fff0a0;--skipped-line-num-bg: #eed860;--missed-branch-bg: #ffd0a0;--missed-branch-line-num-bg: #ffb060;--missed-branch-text: #b45309;--missed-method-bg: #e8d0ff;--missed-method-line-num-bg: #d4b0ff;--missed-method-text: #7b2d8e;--source-bg: #fff;--source-border: #c0c5cc;--source-line-number: #444;--source-line-num-bg: #f0f1f3;--hits-bg: #e0e3e8;--hits-color: #333;--overlay-bg: rgba(0, 0, 0, .5);--bar-bg: #d0d7de;--bar-height: 6px}@media(prefers-color-scheme:dark){:root:not(.light-mode){--bg: #010409;--surface: #0d1117;--surface-alt: #161b22;--text: #f0f3f6;--text-secondary: #b0b8c4;--text-tertiary: #9aa5b1;--zebra: #161b22;--border: #3d444d;--border-strong: #555e68;--accent: #6cb6ff;--accent-hover: #96ccff;--accent-subtle: #121d2f;--green: #56d364;--red: #ff6b61;--yellow: #e3b341;--orange: #f0883e;--covered-bg: #122d1e;--covered-line-num-bg: #1e4430;--missed-bg: #351418;--missed-line-num-bg: #4e1d20;--never-bg: #0d1117;--never-line-num-bg: #161b22;--skipped-bg: #302818;--skipped-line-num-bg: #443920;--missed-branch-bg: #322218;--missed-branch-line-num-bg: #483020;--missed-branch-text: #ffb86c;--missed-method-bg: #1e1830;--missed-method-line-num-bg: #2a2044;--missed-method-text: #dcb8ff;--source-bg: #0d1117;--source-border: #3d444d;--source-line-number: #9aa5b1;--source-line-num-bg: #161b22;--hits-bg: #262c34;--hits-color: #b0b8c4;--overlay-bg: rgba(1, 4, 9, .8);--bar-bg: #3d444d}}.dark-mode{--bg: #010409;--surface: #0d1117;--surface-alt: #161b22;--text: #f0f3f6;--text-secondary: #b0b8c4;--text-tertiary: #9aa5b1;--zebra: #161b22;--border: #3d444d;--border-strong: #555e68;--accent: #6cb6ff;--accent-hover: #96ccff;--accent-subtle: #121d2f;--green: #56d364;--red: #ff6b61;--yellow: #e3b341;--orange: #f0883e;--covered-bg: #122d1e;--covered-line-num-bg: #1e4430;--missed-bg: #351418;--missed-line-num-bg: #4e1d20;--never-bg: #0d1117;--never-line-num-bg: #161b22;--skipped-bg: #302818;--skipped-line-num-bg: #443920;--missed-branch-bg: #322218;--missed-branch-line-num-bg: #483020;--missed-branch-text: #ffb86c;--missed-method-bg: #1e1830;--missed-method-line-num-bg: #2a2044;--missed-method-text: #dcb8ff;--source-bg: #0d1117;--source-border: #3d444d;--source-line-number: #9aa5b1;--source-line-num-bg: #161b22;--hits-bg: #262c34;--hits-color: #b0b8c4;--overlay-bg: rgba(1, 4, 9, .8);--bar-bg: #3d444d}body{font-family:var(--font-sans);font-size:18px;color:var(--text);background:var(--bg);padding:var(--sp-6)}a{color:var(--accent);text-decoration:none;transition:color .15s}a:hover{color:var(--accent-hover)}strong,b{font-weight:600}#loading{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--bg);z-index:9999}#loading-inner{width:280px;text-align:center}#loading-bar-track{width:100%;height:6px;background:var(--bar-bg);border-radius:6px;overflow:hidden}#loading-bar-fill{width:0%;height:100%;background:var(--accent);border-radius:6px;transition:width .15s ease-out}#loading-text{margin-top:var(--sp-3);font-size:18px;color:var(--text-tertiary)}#wrapper{margin:0 auto}.tab-bar{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-4);margin-bottom:-1px;position:relative;z-index:1}abbr.timeago{text-decoration:none;border:none}.group_tabs{display:flex;align-self:flex-end;gap:var(--sp-1);overflow-x:auto}.group_tabs li a{display:block;padding:var(--sp-2) var(--sp-4);font-size:18px;font-weight:500;color:var(--text-secondary);background:transparent;border:1px solid transparent;border-bottom:none;border-radius:var(--radius-md) var(--radius-md) 0 0;white-space:nowrap;transition:color .15s,background .15s}.group_tabs li a:hover{color:var(--text);background:var(--surface-alt);text-decoration:none}.group_tabs li.active a{color:var(--accent);background:var(--surface);border-color:var(--border);font-weight:600}#content{background:var(--surface);border:1px solid var(--border);border-radius:0 var(--radius-lg) var(--radius-lg) var(--radius-lg);padding:var(--sp-6)}.file_list_container h2{font-size:24px;font-weight:600;color:var(--text);margin:0}.file_list_container h2 .covered_percent{font-weight:600}.summary-stats{display:flex;flex-direction:column;gap:var(--sp-1);font-size:18px;color:var(--text-secondary)}.summary-stats b{color:var(--text)}.summary-stats .missed-branch-text b,.summary-stats .missed-method-text-color b,.summary-stats .green b,.summary-stats .red b{color:inherit}.summary-stats .green{color:var(--green)}.summary-stats .red{color:var(--red)}.coverage-disabled{color:var(--text-tertiary);font-style:italic}.th-with-filter{display:flex;align-items:center;gap:var(--sp-2)}table.file_list th.cell--coverage .th-with-filter{white-space:nowrap;justify-content:flex-end}.th-with-filter .th-label{white-space:nowrap}.col-filter--name{width:100%;min-width:200px;border:1px solid var(--border);border-radius:999px;padding:var(--sp-1) var(--sp-4);font-size:14px;background:var(--surface);color:var(--text);outline:none}.col-filter--name:focus{border-color:var(--accent)}.col-filter__coverage{display:flex;gap:var(--sp-1)}.col-filter__op{border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--sp-1) var(--sp-1);font-size:14px;background:var(--surface);color:var(--text);cursor:pointer}.col-filter__value{border:1px solid var(--border);border-radius:var(--radius-sm);padding:var(--sp-1) var(--sp-2);font-size:14px;background:var(--surface);color:var(--text);width:60px;outline:none}.col-filter__value:focus{border-color:var(--accent)}.file_list--responsive{overflow-x:auto}table.file_list{width:100%;font-size:18px}table.file_list{border-collapse:separate;border-spacing:0}table.file_list thead th{font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--text-tertiary);background:var(--surface);padding:var(--sp-2) var(--sp-2);border-bottom:2px solid var(--border-strong);white-space:nowrap}table.file_list tbody tr{background:var(--surface);cursor:pointer}table.file_list tbody tr:nth-child(2n){background:var(--zebra)}table.file_list tbody tr:hover{background:var(--accent-subtle)}table.file_list tbody tr.keyboard-focus{background:var(--accent-subtle);outline:2px solid var(--accent);outline-offset:-2px}table.file_list tbody td{padding:var(--sp-2) var(--sp-2);border-bottom:1px solid var(--border);color:var(--text)}table.file_list td.cell--number{text-align:right;font-variant-numeric:tabular-nums;color:var(--text)}table.file_list th.cell--left,table.file_list th.cell--coverage{text-align:left}table.file_list th.cell--number{text-align:right}table.file_list th.cell--numerator{text-align:right;padding-right:0}table.file_list th.cell--denominator{text-align:left;padding-left:0}table.file_list td.strong{font-weight:600;color:var(--text)}table.file_list td.t-file__name{white-space:nowrap}a.src_link{color:var(--accent);font-weight:500;word-break:break-all}table.file_list td.cell--coverage{white-space:nowrap}.coverage-cell{display:flex;flex-wrap:nowrap;align-items:center;justify-content:flex-end;gap:10px}.coverage-cell .coverage-pct{flex:0 0 4.5em;font-variant-numeric:tabular-nums}.coverage-cell .bar-sizer{flex:0 0 auto;width:240px;min-width:160px;max-width:240px}table.file_list td.cell--numerator{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;padding-left:var(--sp-1);padding-right:0}table.file_list td.cell--denominator{text-align:left;font-variant-numeric:tabular-nums;white-space:nowrap;padding-left:0;padding-right:var(--sp-1)}table.file_list .totals-row td.cell--numerator{color:var(--text);padding-right:0}table.file_list .totals-row td.cell--denominator{color:var(--text);padding-left:0}.coverage-cell__fraction{font-variant-numeric:tabular-nums;color:var(--text-secondary);font-size:14px;white-space:nowrap}.coverage-bar{width:100%;height:var(--bar-height);background:var(--bar-bg);border-radius:6px;overflow:hidden}.coverage-bar__fill{height:100%;border-radius:6px}.coverage-bar__fill--green{background:var(--green)}.coverage-bar__fill--yellow{background:var(--yellow)}.coverage-bar__fill--red{background:var(--red)}.green,table.file_list td.green{color:var(--green)}.red,table.file_list td.red{color:var(--red)}.yellow,table.file_list td.yellow{color:var(--yellow)}.missed-branch-text{color:var(--missed-branch-text)}.missed-method-text-color{color:var(--missed-method-text)}dialog.source-dialog{position:fixed;inset:0;width:100%;height:100%;max-width:100%;max-height:100%;border:none;padding:0;background:var(--bg);color:var(--text);overflow:hidden}dialog.source-dialog::backdrop{background:var(--overlay-bg)}dialog.source-dialog[open]{display:flex;flex-direction:column}.source-dialog__header{display:flex;align-items:flex-start;justify-content:space-between;padding:var(--sp-4) var(--sp-6);background:var(--surface);border-bottom:1px solid var(--border);flex-shrink:0}.source-dialog__title{flex:1;min-width:0}.source-dialog__title h2{font-size:22px;font-weight:700;color:var(--text);margin-bottom:var(--sp-2);word-break:break-all}.source-legend{display:flex;flex-wrap:wrap;gap:var(--sp-2) var(--sp-4);align-items:center;align-self:flex-end;flex-shrink:0;margin-left:auto;padding-left:var(--sp-6)}.source-legend__item{display:flex;align-items:center;gap:var(--sp-1);font-size:13px;color:var(--text-secondary);white-space:nowrap}.source-legend__swatch{display:inline-block;width:14px;height:14px;border-radius:3px;border:1px solid var(--border)}.source-legend__swatch--covered{background:var(--covered-bg);border-color:var(--covered-line-num-bg)}.source-legend__swatch--missed{background:var(--missed-bg);border-color:var(--missed-line-num-bg)}.source-legend__swatch--skipped{background:var(--skipped-bg);border-color:var(--skipped-line-num-bg)}.source-legend__swatch--missed-branch{background:var(--missed-branch-bg);border-color:var(--missed-branch-line-num-bg)}.source-legend__swatch--missed-method{background:var(--missed-method-bg);border-color:var(--missed-method-line-num-bg)}.source-dialog__close{appearance:none;background:none;border:1px solid var(--border);border-radius:50%;width:34px;height:34px;font-size:0;color:var(--text-secondary);cursor:pointer;position:relative;flex-shrink:0;margin-left:var(--sp-4);transition:color .15s,border-color .15s}.source-dialog__close:before,.source-dialog__close:after{content:"";position:absolute;top:50%;left:50%;width:14px;height:2px;background:currentColor;border-radius:1px}.source-dialog__close:before{transform:translate(-50%,-50%) rotate(45deg)}.source-dialog__close:after{transform:translate(-50%,-50%) rotate(-45deg)}.source-dialog__close:hover{color:var(--text);border-color:var(--border-strong)}.source-dialog__body{flex:1;overflow:auto}.source_table .header{padding:var(--sp-4) var(--sp-6);background:var(--surface)}.source_table .header h2{font-size:22px;font-weight:700;color:var(--text);margin-bottom:var(--sp-2)}table.file_list .totals-row td{padding:var(--sp-2) var(--sp-2);font-weight:600;border-bottom:2px solid var(--border-strong);background:var(--surface-alt)}.totals-row .t-file-count{font-size:18px;font-weight:700;color:var(--text)}.t-missed-method-toggle{color:var(--missed-method-text);font-weight:600;cursor:pointer;text-decoration:none}.t-missed-method-toggle:hover{text-decoration:underline;color:var(--missed-method-text)}.t-missed-method-list ul{padding-left:2em;margin-top:var(--sp-1);max-height:200px;overflow-y:auto}.t-missed-method-list li{list-style:none}.source_table pre{margin:0;padding:0;white-space:normal;color:var(--text);font-family:var(--font-mono);font-size:16px;line-height:24px;background:var(--source-bg);border:1px solid var(--source-border);border-top:none}.source_table code{color:inherit;font-family:var(--font-mono)}.source_table pre ol{margin:0;padding:0;list-style:none;counter-reset:linenumber}.source_table pre li{display:flex;counter-increment:linenumber;background:var(--never-bg)}.source_table pre li:before{content:counter(linenumber);flex-shrink:0;width:50px;padding:0 8px;text-align:right;color:var(--source-line-number);background:var(--source-line-num-bg);border-right:1px solid var(--source-border);-webkit-user-select:none;user-select:none;cursor:pointer}.source_table pre li:hover:before{color:var(--text)}.source_table pre li:hover{cursor:pointer}.source_table pre li code{order:1;flex:1;min-width:0;padding:0 12px;white-space:pre-wrap}.source_table pre .hits{order:2;flex-shrink:0;padding:0 var(--sp-2);background:var(--hits-bg);color:var(--hits-color);font-family:var(--font-mono);font-size:14px;text-align:center;line-height:24px;border-left:1px solid var(--source-border);-webkit-user-select:none;user-select:none}.source_table pre .hits:after{content:attr(data-content)}.source_table .covered{background-color:var(--covered-bg)}.source_table .missed{background-color:var(--missed-bg)}.source_table .never{background-color:var(--never-bg)}.source_table .skipped{background-color:var(--skipped-bg)}.source_table .missed-branch{background-color:var(--missed-branch-bg)}.source_table .missed-method{background-color:var(--missed-method-bg)}.source_table .covered:before{background-color:var(--covered-line-num-bg)}.source_table .missed:before{background-color:var(--missed-line-num-bg)}.source_table .never:before{background-color:var(--never-line-num-bg)}.source_table .skipped:before{background-color:var(--skipped-line-num-bg)}.source_table .missed-branch:before{background-color:var(--missed-branch-line-num-bg)}.source_table .missed-method:before{background-color:var(--missed-method-line-num-bg)}#dark-mode-toggle{appearance:none;background:var(--surface);color:var(--text-secondary);border:1px solid var(--border);border-radius:999px;padding:var(--sp-1) var(--sp-4);font-size:16px;font-family:var(--font-sans);cursor:pointer;white-space:nowrap;transition:color .15s,border-color .15s}#dark-mode-toggle:hover{color:var(--text);border-color:var(--border-strong)}#footer{color:var(--text-tertiary);font-size:16px;margin-top:var(--sp-5);text-align:center}#footer a{color:var(--text-secondary);text-decoration:underline}#footer a:hover{color:var(--text)}table.file_list thead th.sorting,table.file_list thead th.sorting_asc,table.file_list thead th.sorting_desc{cursor:pointer;position:relative;padding-right:12px}table.file_list thead th.sorting:after,table.file_list thead th.sorting_asc:after,table.file_list thead th.sorting_desc:after{position:absolute;right:2px;top:50%;transform:translateY(-50%);font-size:14px;color:var(--text-tertiary)}table.file_list thead th.sorting:after{content:"\2195"}table.file_list thead th.sorting_asc:after{content:"\2191"}table.file_list thead th.sorting_desc:after{content:"\2193"}@media print{:root,.dark-mode{--bg: #fff;--surface: #fff;--surface-alt: #f4f5f7;--text: #111;--text-secondary: #333;--text-tertiary: #444;--zebra: #f4f5f7;--border: #c0c5cc;--border-strong: #999;--accent: #0550ae;--green: #116329;--red: #a40e26;--yellow: #7a5200;--orange: #953800;--bar-bg: #d0d7de;--covered-bg: #ccf5d0;--covered-line-num-bg: #9ae6a4;--missed-bg: #ffd8d5;--missed-line-num-bg: #ffb8b3;--never-bg: #fff;--never-line-num-bg: #f0f1f3;--skipped-bg: #fff0a0;--skipped-line-num-bg: #eed860;--missed-branch-bg: #ffd0a0;--missed-branch-line-num-bg: #ffb060;--missed-branch-text: #b45309;--missed-method-bg: #e8d0ff;--missed-method-line-num-bg: #d4b0ff;--missed-method-text: #7b2d8e;--source-bg: #fff;--source-border: #c0c5cc;--source-line-number: #444;--source-line-num-bg: #f0f1f3;--hits-bg: #e0e3e8;--hits-color: #333}body{padding:0;font-size:12pt}#loading,#dark-mode-toggle,.tab-bar,.source_files,.col-filter--name,.col-filter__coverage,table.file_list thead th.sorting:after,table.file_list thead th.sorting_asc:after,table.file_list thead th.sorting_desc:after{display:none!important}#wrapper,.file_list_container{display:block!important}body:has(.source-dialog[open]) #wrapper{display:none!important}#content{border:none;border-radius:0;padding:0}.file_list_container .group_name{display:inline!important;font-size:16pt;font-weight:700}.file_list_container .covered_percent{display:inline!important;font-weight:600}.file_list_container+.file_list_container{margin-top:24pt}dialog.source-dialog{display:none}dialog.source-dialog[open]{display:block!important;position:static;width:100%;height:auto;max-height:none;overflow:visible;background:#fff}.source-dialog__close{display:none!important}.source-dialog__header{flex-wrap:wrap;border-bottom:none;padding:0 0 8pt}.source-dialog__title{flex:0 0 100%}.source-dialog__title h2{word-break:normal;overflow-wrap:break-word}.source-legend{margin-left:0;padding-left:0;padding-top:var(--sp-2)}.source-dialog__body{overflow:visible}.source_table pre{font-size:8pt;line-height:1.4}.source_table pre li{-webkit-print-color-adjust:exact;print-color-adjust:exact}.source_table pre li:before{-webkit-print-color-adjust:exact;print-color-adjust:exact}.source_table pre .hits{-webkit-print-color-adjust:exact;print-color-adjust:exact}.coverage-bar{border:1px solid var(--border);-webkit-print-color-adjust:exact;print-color-adjust:exact}.coverage-bar__fill{-webkit-print-color-adjust:exact;print-color-adjust:exact}table.file_list tbody tr:nth-child(2n){-webkit-print-color-adjust:exact;print-color-adjust:exact}table.file_list{font-size:10pt}table.file_list thead th{font-size:9pt}table.file_list{page-break-inside:auto}table.file_list tr{page-break-inside:avoid}table.file_list thead{display:table-header-group}#footer{font-size:10pt;margin-top:12pt}#footer a:after{content:" (" attr(href) ")";font-size:9pt;color:var(--text-tertiary)}}
@@ -0,0 +1,18 @@
1
+ "use strict";(()=>{var hn=Object.create;var ct=Object.defineProperty;var mn=Object.getOwnPropertyDescriptor;var bn=Object.getOwnPropertyNames;var _n=Object.getPrototypeOf,En=Object.prototype.hasOwnProperty;var vn=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var yn=(e,t,n,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of bn(t))!En.call(e,i)&&i!==n&&ct(e,i,{get:()=>t[i],enumerable:!(s=mn(t,i))||s.enumerable});return e};var Mn=(e,t,n)=>(n=e!=null?hn(_n(e)):{},yn(t||!e||!e.__esModule?ct(n,"default",{value:e,enumerable:!0}):n,e));var J=(e,t,n)=>new Promise((s,i)=>{var c=l=>{try{o(n.next(l))}catch(d){i(d)}},r=l=>{try{o(n.throw(l))}catch(d){i(d)}},o=l=>l.done?s(l.value):Promise.resolve(l.value).then(c,r);o((n=n.apply(e,t)).next())});var $t=vn((Zs,It)=>{function yt(e){return e instanceof Map?e.clear=e.delete=e.set=function(){throw new Error("map is read-only")}:e instanceof Set&&(e.add=e.clear=e.delete=function(){throw new Error("set is read-only")}),Object.freeze(e),Object.getOwnPropertyNames(e).forEach(t=>{let n=e[t],s=typeof n;(s==="object"||s==="function")&&!Object.isFrozen(n)&&yt(n)}),e}var Ee=class{constructor(t){t.data===void 0&&(t.data={}),this.data=t.data,this.isMatchIgnored=!1}ignoreMatch(){this.isMatchIgnored=!0}};function Mt(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;")}function G(e,...t){let n=Object.create(null);for(let s in e)n[s]=e[s];return t.forEach(function(s){for(let i in s)n[i]=s[i]}),n}var Ln="</span>",ht=e=>!!e.scope,xn=(e,{prefix:t})=>{if(e.startsWith("language:"))return e.replace("language:","language-");if(e.includes(".")){let n=e.split(".");return[`${t}${n.shift()}`,...n.map((s,i)=>`${s}${"_".repeat(i+1)}`)].join(" ")}return`${t}${e}`},$e=class{constructor(t,n){this.buffer="",this.classPrefix=n.classPrefix,t.walk(this)}addText(t){this.buffer+=Mt(t)}openNode(t){if(!ht(t))return;let n=xn(t.scope,{prefix:this.classPrefix});this.span(n)}closeNode(t){ht(t)&&(this.buffer+=Ln)}value(){return this.buffer}span(t){this.buffer+=`<span class="${t}">`}},mt=(e={})=>{let t={children:[]};return Object.assign(t,e),t},He=class e{constructor(){this.rootNode=mt(),this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(t){this.top.children.push(t)}openNode(t){let n=mt({scope:t});this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(t){return this.constructor._walk(t,this.rootNode)}static _walk(t,n){return typeof n=="string"?t.addText(n):n.children&&(t.openNode(n),n.children.forEach(s=>this._walk(t,s)),t.closeNode(n)),t}static _collapse(t){typeof t!="string"&&t.children&&(t.children.every(n=>typeof n=="string")?t.children=[t.children.join("")]:t.children.forEach(n=>{e._collapse(n)}))}},ke=class extends He{constructor(t){super(),this.options=t}addText(t){t!==""&&this.add(t)}startScope(t){this.openNode(t)}endScope(){this.closeNode()}__addSublanguage(t,n){let s=t.root;n&&(s.scope=`language:${n}`),this.add(s)}toHTML(){return new $e(this,this.options).value()}finalize(){return this.closeAllNodes(),!0}};function le(e){return e?typeof e=="string"?e:e.source:null}function wt(e){return V("(?=",e,")")}function An(e){return V("(?:",e,")*")}function Cn(e){return V("(?:",e,")?")}function V(...e){return e.map(n=>le(n)).join("")}function Rn(e){let t=e[e.length-1];return typeof t=="object"&&t.constructor===Object?(e.splice(e.length-1,1),t):{}}function Be(...e){return"("+(Rn(e).capture?"":"?:")+e.map(s=>le(s)).join("|")+")"}function Tt(e){return new RegExp(e.toString()+"|").exec("").length-1}function Nn(e,t){let n=e&&e.exec(t);return n&&n.index===0}var On=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;function Pe(e,{joinWith:t}){let n=0;return e.map(s=>{n+=1;let i=n,c=le(s),r="";for(;c.length>0;){let o=On.exec(c);if(!o){r+=c;break}r+=c.substring(0,o.index),c=c.substring(o.index+o[0].length),o[0][0]==="\\"&&o[1]?r+="\\"+String(Number(o[1])+i):(r+=o[0],o[0]==="("&&n++)}return r}).map(s=>`(${s})`).join(t)}var In=/\b\B/,St="[a-zA-Z]\\w*",Fe="[a-zA-Z_]\\w*",Lt="\\b\\d+(\\.\\d+)?",xt="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",At="\\b(0b[01]+)",$n="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",Hn=(e={})=>{let t=/^#![ ]*\//;return e.binary&&(e.begin=V(t,/.*\b/,e.binary,/\b.*/)),G({scope:"meta",begin:t,end:/$/,relevance:0,"on:begin":(n,s)=>{n.index!==0&&s.ignoreMatch()}},e)},ae={begin:"\\\\[\\s\\S]",relevance:0},kn={scope:"string",begin:"'",end:"'",illegal:"\\n",contains:[ae]},Dn={scope:"string",begin:'"',end:'"',illegal:"\\n",contains:[ae]},Bn={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},ye=function(e,t,n={}){let s=G({scope:"comment",begin:e,end:t,contains:[]},n);s.contains.push({scope:"doctag",begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)",end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0});let i=Be("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/);return s.contains.push({begin:V(/[ ]+/,"(",i,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),s},Pn=ye("//","$"),Fn=ye("/\\*","\\*/"),Un=ye("#","$"),Wn={scope:"number",begin:Lt,relevance:0},jn={scope:"number",begin:xt,relevance:0},qn={scope:"number",begin:At,relevance:0},zn={scope:"regexp",begin:/\/(?=[^/\n]*\/)/,end:/\/[gimuy]*/,contains:[ae,{begin:/\[/,end:/\]/,relevance:0,contains:[ae]}]},Gn={scope:"title",begin:St,relevance:0},Kn={scope:"title",begin:Fe,relevance:0},Xn={begin:"\\.\\s*"+Fe,relevance:0},Zn=function(e){return Object.assign(e,{"on:begin":(t,n)=>{n.data._beginMatch=t[1]},"on:end":(t,n)=>{n.data._beginMatch!==t[1]&&n.ignoreMatch()}})},_e=Object.freeze({__proto__:null,APOS_STRING_MODE:kn,BACKSLASH_ESCAPE:ae,BINARY_NUMBER_MODE:qn,BINARY_NUMBER_RE:At,COMMENT:ye,C_BLOCK_COMMENT_MODE:Fn,C_LINE_COMMENT_MODE:Pn,C_NUMBER_MODE:jn,C_NUMBER_RE:xt,END_SAME_AS_BEGIN:Zn,HASH_COMMENT_MODE:Un,IDENT_RE:St,MATCH_NOTHING_RE:In,METHOD_GUARD:Xn,NUMBER_MODE:Wn,NUMBER_RE:Lt,PHRASAL_WORDS_MODE:Bn,QUOTE_STRING_MODE:Dn,REGEXP_MODE:zn,RE_STARTERS_RE:$n,SHEBANG:Hn,TITLE_MODE:Gn,UNDERSCORE_IDENT_RE:Fe,UNDERSCORE_TITLE_MODE:Kn});function Vn(e,t){e.input[e.index-1]==="."&&t.ignoreMatch()}function Yn(e,t){e.className!==void 0&&(e.scope=e.className,delete e.className)}function Qn(e,t){t&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)",e.__beforeBegin=Vn,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords,e.relevance===void 0&&(e.relevance=0))}function Jn(e,t){Array.isArray(e.illegal)&&(e.illegal=Be(...e.illegal))}function es(e,t){if(e.match){if(e.begin||e.end)throw new Error("begin & end are not supported with match");e.begin=e.match,delete e.match}}function ts(e,t){e.relevance===void 0&&(e.relevance=1)}var ns=(e,t)=>{if(!e.beforeMatch)return;if(e.starts)throw new Error("beforeMatch cannot be used with starts");let n=Object.assign({},e);Object.keys(e).forEach(s=>{delete e[s]}),e.keywords=n.keywords,e.begin=V(n.beforeMatch,wt(n.begin)),e.starts={relevance:0,contains:[Object.assign(n,{endsParent:!0})]},e.relevance=0,delete n.beforeMatch},ss=["of","and","for","in","not","or","if","then","parent","list","value"],is="keyword";function Ct(e,t,n=is){let s=Object.create(null);return typeof e=="string"?i(n,e.split(" ")):Array.isArray(e)?i(n,e):Object.keys(e).forEach(function(c){Object.assign(s,Ct(e[c],t,c))}),s;function i(c,r){t&&(r=r.map(o=>o.toLowerCase())),r.forEach(function(o){let l=o.split("|");s[l[0]]=[c,rs(l[0],l[1])]})}}function rs(e,t){return t?Number(t):os(e)?0:1}function os(e){return ss.includes(e.toLowerCase())}var bt={},Z=e=>{console.error(e)},_t=(e,...t)=>{console.log(`WARN: ${e}`,...t)},te=(e,t)=>{bt[`${e}/${t}`]||(console.log(`Deprecated as of ${e}. ${t}`),bt[`${e}/${t}`]=!0)},ve=new Error;function Rt(e,t,{key:n}){let s=0,i=e[n],c={},r={};for(let o=1;o<=t.length;o++)r[o+s]=i[o],c[o+s]=!0,s+=Tt(t[o-1]);e[n]=r,e[n]._emit=c,e[n]._multi=!0}function cs(e){if(Array.isArray(e.begin)){if(e.skip||e.excludeBegin||e.returnBegin)throw Z("skip, excludeBegin, returnBegin not compatible with beginScope: {}"),ve;if(typeof e.beginScope!="object"||e.beginScope===null)throw Z("beginScope must be object"),ve;Rt(e,e.begin,{key:"beginScope"}),e.begin=Pe(e.begin,{joinWith:""})}}function ls(e){if(Array.isArray(e.end)){if(e.skip||e.excludeEnd||e.returnEnd)throw Z("skip, excludeEnd, returnEnd not compatible with endScope: {}"),ve;if(typeof e.endScope!="object"||e.endScope===null)throw Z("endScope must be object"),ve;Rt(e,e.end,{key:"endScope"}),e.end=Pe(e.end,{joinWith:""})}}function as(e){e.scope&&typeof e.scope=="object"&&e.scope!==null&&(e.beginScope=e.scope,delete e.scope)}function us(e){as(e),typeof e.beginScope=="string"&&(e.beginScope={_wrap:e.beginScope}),typeof e.endScope=="string"&&(e.endScope={_wrap:e.endScope}),cs(e),ls(e)}function ds(e){function t(r,o){return new RegExp(le(r),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(o?"g":""))}class n{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(o,l){l.position=this.position++,this.matchIndexes[this.matchAt]=l,this.regexes.push([l,o]),this.matchAt+=Tt(o)+1}compile(){this.regexes.length===0&&(this.exec=()=>null);let o=this.regexes.map(l=>l[1]);this.matcherRe=t(Pe(o,{joinWith:"|"}),!0),this.lastIndex=0}exec(o){this.matcherRe.lastIndex=this.lastIndex;let l=this.matcherRe.exec(o);if(!l)return null;let d=l.findIndex((E,w)=>w>0&&E!==void 0),p=this.matchIndexes[d];return l.splice(0,d),Object.assign(l,p)}}class s{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(o){if(this.multiRegexes[o])return this.multiRegexes[o];let l=new n;return this.rules.slice(o).forEach(([d,p])=>l.addRule(d,p)),l.compile(),this.multiRegexes[o]=l,l}resumingScanAtSamePosition(){return this.regexIndex!==0}considerAll(){this.regexIndex=0}addRule(o,l){this.rules.push([o,l]),l.type==="begin"&&this.count++}exec(o){let l=this.getMatcher(this.regexIndex);l.lastIndex=this.lastIndex;let d=l.exec(o);if(this.resumingScanAtSamePosition()&&!(d&&d.index===this.lastIndex)){let p=this.getMatcher(0);p.lastIndex=this.lastIndex+1,d=p.exec(o)}return d&&(this.regexIndex+=d.position+1,this.regexIndex===this.count&&this.considerAll()),d}}function i(r){let o=new s;return r.contains.forEach(l=>o.addRule(l.begin,{rule:l,type:"begin"})),r.terminatorEnd&&o.addRule(r.terminatorEnd,{type:"end"}),r.illegal&&o.addRule(r.illegal,{type:"illegal"}),o}function c(r,o){let l=r;if(r.isCompiled)return l;[Yn,es,us,ns].forEach(p=>p(r,o)),e.compilerExtensions.forEach(p=>p(r,o)),r.__beforeBegin=null,[Qn,Jn,ts].forEach(p=>p(r,o)),r.isCompiled=!0;let d=null;return typeof r.keywords=="object"&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords),d=r.keywords.$pattern,delete r.keywords.$pattern),d=d||/\w+/,r.keywords&&(r.keywords=Ct(r.keywords,e.case_insensitive)),l.keywordPatternRe=t(d,!0),o&&(r.begin||(r.begin=/\B|\b/),l.beginRe=t(l.begin),!r.end&&!r.endsWithParent&&(r.end=/\B|\b/),r.end&&(l.endRe=t(l.end)),l.terminatorEnd=le(l.end)||"",r.endsWithParent&&o.terminatorEnd&&(l.terminatorEnd+=(r.end?"|":"")+o.terminatorEnd)),r.illegal&&(l.illegalRe=t(r.illegal)),r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map(function(p){return fs(p==="self"?r:p)})),r.contains.forEach(function(p){c(p,l)}),r.starts&&c(r.starts,o),l.matcher=i(l),l}if(e.compilerExtensions||(e.compilerExtensions=[]),e.contains&&e.contains.includes("self"))throw new Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return e.classNameAliases=G(e.classNameAliases||{}),c(e)}function Nt(e){return e?e.endsWithParent||Nt(e.starts):!1}function fs(e){return e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map(function(t){return G(e,{variants:null},t)})),e.cachedVariants?e.cachedVariants:Nt(e)?G(e,{starts:e.starts?G(e.starts):null}):Object.isFrozen(e)?G(e):e}var gs="11.11.1",De=class extends Error{constructor(t,n){super(t),this.name="HTMLInjectionError",this.html=n}},Ie=Mt,Et=G,vt=Symbol("nomatch"),ps=7,Ot=function(e){let t=Object.create(null),n=Object.create(null),s=[],i=!0,c="Could not find the language '{}', did you forget to load/include a language module?",r={disableAutodetect:!0,name:"Plain text",contains:[]},o={ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",cssSelector:"pre code",languages:null,__emitter:ke};function l(a){return o.noHighlightRe.test(a)}function d(a){let g=a.className+" ";g+=a.parentNode?a.parentNode.className:"";let b=o.languageDetectRe.exec(g);if(b){let y=U(b[1]);return y||(_t(c.replace("{}",b[1])),_t("Falling back to no-highlight mode for this block.",a)),y?b[1]:"no-highlight"}return g.split(/\s+/).find(y=>l(y)||U(y))}function p(a,g,b){let y="",S="";typeof g=="object"?(y=a,b=g.ignoreIllegals,S=g.language):(te("10.7.0","highlight(lang, code, ...args) has been deprecated."),te("10.7.0",`Please use highlight(code, options) instead.
2
+ https://github.com/highlightjs/highlight.js/issues/2277`),S=a,y=g),b===void 0&&(b=!0);let k={code:y,language:S};pe("before:highlight",k);let z=k.result?k.result:E(k.language,k.code,b);return z.code=k.code,pe("after:highlight",z),z}function E(a,g,b,y){let S=Object.create(null);function k(u,f){return u.keywords[f]}function z(){if(!h.keywords){L.addText(M);return}let u=0;h.keywordPatternRe.lastIndex=0;let f=h.keywordPatternRe.exec(M),m="";for(;f;){m+=M.substring(u,f.index);let v=B.case_insensitive?f[0].toLowerCase():f[0],x=k(h,v);if(x){let[W,gn]=x;if(L.addText(m),m="",S[v]=(S[v]||0)+1,S[v]<=ps&&(be+=gn),W.startsWith("_"))m+=f[0];else{let pn=B.classNameAliases[W]||W;D(f[0],pn)}}else m+=f[0];u=h.keywordPatternRe.lastIndex,f=h.keywordPatternRe.exec(M)}m+=M.substring(u),L.addText(m)}function he(){if(M==="")return;let u=null;if(typeof h.subLanguage=="string"){if(!t[h.subLanguage]){L.addText(M);return}u=E(h.subLanguage,M,!0,ot[h.subLanguage]),ot[h.subLanguage]=u._top}else u=T(M,h.subLanguage.length?h.subLanguage:null);h.relevance>0&&(be+=u.relevance),L.__addSublanguage(u._emitter,u.language)}function N(){h.subLanguage!=null?he():z(),M=""}function D(u,f){u!==""&&(L.startScope(f),L.addText(u),L.endScope())}function nt(u,f){let m=1,v=f.length-1;for(;m<=v;){if(!u._emit[m]){m++;continue}let x=B.classNameAliases[u[m]]||u[m],W=f[m];x?D(W,x):(M=W,z(),M=""),m++}}function st(u,f){return u.scope&&typeof u.scope=="string"&&L.openNode(B.classNameAliases[u.scope]||u.scope),u.beginScope&&(u.beginScope._wrap?(D(M,B.classNameAliases[u.beginScope._wrap]||u.beginScope._wrap),M=""):u.beginScope._multi&&(nt(u.beginScope,f),M="")),h=Object.create(u,{parent:{value:h}}),h}function it(u,f,m){let v=Nn(u.endRe,m);if(v){if(u["on:end"]){let x=new Ee(u);u["on:end"](f,x),x.isMatchIgnored&&(v=!1)}if(v){for(;u.endsParent&&u.parent;)u=u.parent;return u}}if(u.endsWithParent)return it(u.parent,f,m)}function ln(u){return h.matcher.regexIndex===0?(M+=u[0],1):(Oe=!0,0)}function an(u){let f=u[0],m=u.rule,v=new Ee(m),x=[m.__beforeBegin,m["on:begin"]];for(let W of x)if(W&&(W(u,v),v.isMatchIgnored))return ln(f);return m.skip?M+=f:(m.excludeBegin&&(M+=f),N(),!m.returnBegin&&!m.excludeBegin&&(M=f)),st(m,u),m.returnBegin?0:f.length}function un(u){let f=u[0],m=g.substring(u.index),v=it(h,u,m);if(!v)return vt;let x=h;h.endScope&&h.endScope._wrap?(N(),D(f,h.endScope._wrap)):h.endScope&&h.endScope._multi?(N(),nt(h.endScope,u)):x.skip?M+=f:(x.returnEnd||x.excludeEnd||(M+=f),N(),x.excludeEnd&&(M=f));do h.scope&&L.closeNode(),!h.skip&&!h.subLanguage&&(be+=h.relevance),h=h.parent;while(h!==v.parent);return v.starts&&st(v.starts,u),x.returnEnd?0:f.length}function dn(){let u=[];for(let f=h;f!==B;f=f.parent)f.scope&&u.unshift(f.scope);u.forEach(f=>L.openNode(f))}let me={};function rt(u,f){let m=f&&f[0];if(M+=u,m==null)return N(),0;if(me.type==="begin"&&f.type==="end"&&me.index===f.index&&m===""){if(M+=g.slice(f.index,f.index+1),!i){let v=new Error(`0 width match regex (${a})`);throw v.languageName=a,v.badRule=me.rule,v}return 1}if(me=f,f.type==="begin")return an(f);if(f.type==="illegal"&&!b){let v=new Error('Illegal lexeme "'+m+'" for mode "'+(h.scope||"<unnamed>")+'"');throw v.mode=h,v}else if(f.type==="end"){let v=un(f);if(v!==vt)return v}if(f.type==="illegal"&&m==="")return M+=`
3
+ `,1;if(Ne>1e5&&Ne>f.index*3)throw new Error("potential infinite loop, way more iterations than matches");return M+=m,m.length}let B=U(a);if(!B)throw Z(c.replace("{}",a)),new Error('Unknown language: "'+a+'"');let fn=ds(B),Re="",h=y||fn,ot={},L=new o.__emitter(o);dn();let M="",be=0,X=0,Ne=0,Oe=!1;try{if(B.__emitTokens)B.__emitTokens(g,L);else{for(h.matcher.considerAll();;){Ne++,Oe?Oe=!1:h.matcher.considerAll(),h.matcher.lastIndex=X;let u=h.matcher.exec(g);if(!u)break;let f=g.substring(X,u.index),m=rt(f,u);X=u.index+m}rt(g.substring(X))}return L.finalize(),Re=L.toHTML(),{language:a,value:Re,relevance:be,illegal:!1,_emitter:L,_top:h}}catch(u){if(u.message&&u.message.includes("Illegal"))return{language:a,value:Ie(g),illegal:!0,relevance:0,_illegalBy:{message:u.message,index:X,context:g.slice(X-100,X+100),mode:u.mode,resultSoFar:Re},_emitter:L};if(i)return{language:a,value:Ie(g),illegal:!1,relevance:0,errorRaised:u,_emitter:L,_top:h};throw u}}function w(a){let g={value:Ie(a),illegal:!1,relevance:0,_top:r,_emitter:new o.__emitter(o)};return g._emitter.addText(a),g}function T(a,g){g=g||o.languages||Object.keys(t);let b=w(a),y=g.filter(U).filter(ge).map(N=>E(N,a,!1));y.unshift(b);let S=y.sort((N,D)=>{if(N.relevance!==D.relevance)return D.relevance-N.relevance;if(N.language&&D.language){if(U(N.language).supersetOf===D.language)return 1;if(U(D.language).supersetOf===N.language)return-1}return 0}),[k,z]=S,he=k;return he.secondBest=z,he}function H(a,g,b){let y=g&&n[g]||b;a.classList.add("hljs"),a.classList.add(`language-${y}`)}function A(a){let g=null,b=d(a);if(l(b))return;if(pe("before:highlightElement",{el:a,language:b}),a.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",a);return}if(a.children.length>0&&(o.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(a)),o.throwUnescapedHTML))throw new De("One of your code blocks includes unescaped HTML.",a.innerHTML);g=a;let y=g.textContent,S=b?p(y,{language:b,ignoreIllegals:!0}):T(y);a.innerHTML=S.value,a.dataset.highlighted="yes",H(a,b,S.language),a.result={language:S.language,re:S.relevance,relevance:S.relevance},S.secondBest&&(a.secondBest={language:S.secondBest.language,relevance:S.secondBest.relevance}),pe("after:highlightElement",{el:a,result:S,text:y})}function C(a){o=Et(o,a)}let oe=()=>{ce(),te("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function fe(){ce(),te("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let Ae=!1;function ce(){function a(){ce()}if(document.readyState==="loading"){Ae||window.addEventListener("DOMContentLoaded",a,!1),Ae=!0;return}document.querySelectorAll(o.cssSelector).forEach(A)}function et(a,g){let b=null;try{b=g(e)}catch(y){if(Z("Language definition for '{}' could not be registered.".replace("{}",a)),i)Z(y);else throw y;b=r}b.name||(b.name=a),t[a]=b,b.rawDefinition=g.bind(null,e),b.aliases&&Ce(b.aliases,{languageName:a})}function K(a){delete t[a];for(let g of Object.keys(n))n[g]===a&&delete n[g]}function tt(){return Object.keys(t)}function U(a){return a=(a||"").toLowerCase(),t[a]||t[n[a]]}function Ce(a,{languageName:g}){typeof a=="string"&&(a=[a]),a.forEach(b=>{n[b.toLowerCase()]=g})}function ge(a){let g=U(a);return g&&!g.disableAutodetect}function sn(a){a["before:highlightBlock"]&&!a["before:highlightElement"]&&(a["before:highlightElement"]=g=>{a["before:highlightBlock"](Object.assign({block:g.el},g))}),a["after:highlightBlock"]&&!a["after:highlightElement"]&&(a["after:highlightElement"]=g=>{a["after:highlightBlock"](Object.assign({block:g.el},g))})}function rn(a){sn(a),s.push(a)}function on(a){let g=s.indexOf(a);g!==-1&&s.splice(g,1)}function pe(a,g){let b=a;s.forEach(function(y){y[b]&&y[b](g)})}function cn(a){return te("10.7.0","highlightBlock will be removed entirely in v12.0"),te("10.7.0","Please use highlightElement now."),A(a)}Object.assign(e,{highlight:p,highlightAuto:T,highlightAll:ce,highlightElement:A,highlightBlock:cn,configure:C,initHighlighting:oe,initHighlightingOnLoad:fe,registerLanguage:et,unregisterLanguage:K,listLanguages:tt,getLanguage:U,registerAliases:Ce,autoDetection:ge,inherit:Et,addPlugin:rn,removePlugin:on}),e.debugMode=function(){i=!1},e.safeMode=function(){i=!0},e.versionString=gs,e.regex={concat:V,lookahead:wt,either:Be,optional:Cn,anyNumberOfTimes:An};for(let a in _e)typeof _e[a]=="object"&&yt(_e[a]);return Object.assign(e,_e),e},ne=Ot({});ne.newInstance=()=>Ot({});It.exports=ne;ne.HighlightJS=ne;ne.default=ne});function R(e,t){return(t||document).querySelector(e)}function _(e,t){return Array.from((t||document).querySelectorAll(e))}function P(e,t,n,s){typeof n=="function"?e.addEventListener(t,n):e.addEventListener(t,function(i){let c=i.target.closest(n);c&&e.contains(c)&&s&&s.call(c,i)})}var wn={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"};function I(e){return e.replace(/[&<>"']/g,t=>wn[t])}function lt(e){return J(this,null,function*(){let t=new TextEncoder().encode(e),n=yield crypto.subtle.digest("SHA-1",t);return Array.from(new Uint8Array(n,0,4),s=>s.toString(16).padStart(2,"0")).join("")})}var Tn=90,Sn=75;function F(e){return e>=Tn?"green":e>=Sn?"yellow":"red"}function O(e){return e.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",")}function j(e){return(Math.floor(e*100)/100).toFixed(2)}function at(e){return"g-"+e.replace(/[^a-zA-Z0-9_-]/gu,t=>`_${t.codePointAt(0).toString(16)}_`)}var ut=[[31536e3,"year"],[2592e3,"month"],[86400,"day"],[3600,"hour"],[60,"minute"],[1,"second"]];function dt(e){let t=Math.floor((Date.now()-e.getTime())/1e3);for(let[n,s]of ut){let i=Math.floor(t/n);if(i>=1)return i===1?`about 1 ${s} ago`:`${i} ${s}s ago`}return"just now"}function ft(e){let t=(Date.now()-e.getTime())/1e3;for(let[n]of ut){let s=Math.floor(t/n);if(s>=1){let i=(s+1)*n;return Math.max((i-t)*1e3+500,1e3)}}return 1e3}var gt={};function ee(e){return gt[e]}function pt(e){return J(this,null,function*(){let t=yield Promise.all(e.map(lt));e.forEach((n,s)=>{gt[n]=t[s]})})}var Ht=Mn($t(),1);var Ue=Ht.default;function kt(e){let t=e.regex,n="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",s=t.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=t.concat(s,/(::\w+)*/),r={"variable.constant":["__FILE__","__LINE__","__ENCODING__"],"variable.language":["self","super"],keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield",...["include","extend","prepend","public","private","protected","raise","throw"]],built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"],literal:["true","false","nil"]},o={className:"doctag",begin:"@[A-Za-z]+"},l={begin:"#<",end:">"},d=[e.COMMENT("#","$",{contains:[o]}),e.COMMENT("^=begin","^=end",{contains:[o],relevance:10}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],p={className:"subst",begin:/#\{/,end:/\}/,keywords:r},E={className:"string",contains:[e.BACKSLASH_ESCAPE,p],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?</,end:/>/},{begin:/%[qQwWx]?\//,end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{begin:t.concat(/<<[-~]?'?/,t.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)),contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,p]})]}]},w="[1-9](_?[0-9])*|0",T="[0-9](_?[0-9])*",H={className:"number",relevance:0,variants:[{begin:`\\b(${w})(\\.(${T}))?([eE][+-]?(${T})|r)?i?\\b`},{begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b"},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{begin:"\\b0(_?[0-7])+r?i?\\b"}]},A={variants:[{match:/\(\)/},{className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0,keywords:r}]},K=[E,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class",4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,i],scope:{2:"title.class"},keywords:r},{relevance:0,match:[i,/\.new[. (]/],scope:{1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/,className:"variable.constant"},{relevance:0,match:s,scope:"title.class"},{match:[/def/,/\s+/,n],scope:{1:"keyword",3:"title.function"},contains:[A]},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[E,{begin:n}],relevance:0},H,{className:"variable",begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{className:"params",begin:/\|(?!=)/,end:/\|/,excludeBegin:!0,excludeEnd:!0,relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,p],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(l,d),relevance:0}].concat(l,d);p.contains=K,A.contains=K;let ge=[{begin:/^\s*=>/,starts:{end:"$",contains:K}},{className:"meta.prompt",begin:"^("+"[>?]>"+"|"+"[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]"+"|"+"(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>"+")(?=[ ])",starts:{end:"$",keywords:r,contains:K}}];return d.unshift(l),{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/,contains:[e.SHEBANG({binary:"ruby"})].concat(ge).concat(d).concat(K)}}function je(e){let t=F(e),n=j(e);return`<div class="bar-sizer"><div class="coverage-bar"><div class="coverage-bar__fill coverage-bar__fill--${t}" style="width: ${n}%"></div></div></div>`}function Y(e,t,n,s,i){let c=F(e),r=j(e),o=`<div class="coverage-cell">${je(e)}<span class="coverage-pct">${r}%</span></div>`;if(i)return`<td class="cell--coverage strong t-totals__${s}-pct ${c}">${o}</td><td class="cell--numerator strong t-totals__${s}-num">${O(t)}/</td><td class="cell--denominator strong t-totals__${s}-den">${O(n)}</td>`;let l=` data-order="${j(e)}"`;return`<td class="cell--coverage cell--${s}-pct ${c}"${l}>${o}</td><td class="cell--numerator">${O(t)}/</td><td class="cell--denominator">${O(n)}</td>`}function Me(e,t,n,s){return`<th class="cell--coverage">
4
+ <div class="th-with-filter">
5
+ <span class="th-label">${e}</span>
6
+ <div class="col-filter__coverage">
7
+ <select class="col-filter__op" data-type="${t}"><option value="lt">&lt;</option><option value="lte" selected>&le;</option><option value="eq">=</option><option value="gte">&ge;</option><option value="gt">&gt;</option></select>
8
+ <span class="col-filter__pct-wrap"><input type="number" class="col-filter__value" min="0" max="100" data-type="${t}" value="100" step="any"></span>
9
+ </div>
10
+ </div>
11
+ </th>
12
+ <th class="cell--numerator">${n}</th>
13
+ <th class="cell--denominator">${s}</th>`}function We(e){let{type:t,label:n,covered:s,total:i,enabled:c,toggle:r}=e;if(!c)return`<div class="t-${t}-summary">
14
+ ${n}: <span class="coverage-disabled">disabled</span>
15
+ </div>`;let o=i-s,l=i>0?s*100/i:100,d=F(l),p=e.suffix||"covered",E=e.missedClass||"red",w=`<div class="t-${t}-summary">
16
+ ${n}: <span class="${d}"><b>${j(l)}%</b></span><span class="coverage-cell__fraction"> ${s}/${i} ${p}</span>`;if(o>0){let T=r?`<a href="#" class="t-missed-method-toggle"><b>${o}</b> missed</a>`:`<span class="${E}"><b>${o}</b> missed</span>`;w+=`<span class="coverage-cell__fraction">,</span>
17
+ ${T}`}return w+=`
18
+ </div>`,w}function Dt(e){return'<div class="summary-stats">'+We({type:"line",label:"Line coverage",covered:e.coveredLines,total:e.totalLines,enabled:!0,suffix:"relevant lines covered"})+We({type:"branch",label:"Branch coverage",covered:e.coveredBranches,total:e.totalBranches,enabled:e.branchCoverage,missedClass:"missed-branch-text"})+We({type:"method",label:"Method coverage",covered:e.coveredMethods,total:e.totalMethods,enabled:e.methodCoverage,missedClass:"missed-method-text-color",toggle:e.showMethodToggle})+"</div>"}function hs(e){let{title:t,filenames:n,stats:s,branchCoverage:i,methodCoverage:c}=e,r=at(t),o=s.lines,l=i?s.branches:void 0,d=c?s.methods:void 0,p=[`<div class="file_list_container" id="${r}" data-total-files="${n.length}">`,`<span class="group_name hide">${I(t)}</span>`,`<span class="covered_percent hide"><span class="${F(o.percent)}">${j(o.percent)}%</span></span>`,'<div class="file_list--responsive"><table class="file_list"><thead><tr>','<th class="cell--left"><div class="th-with-filter"><span class="th-label">File Name</span><input type="search" class="col-filter col-filter--name" placeholder="Filter paths\u2026"></div></th>',Me("Line Coverage","line","Covered","Lines")];i&&p.push(Me("Branch Coverage","branch","Covered","Branches")),c&&p.push(Me("Method Coverage","method","Covered","Methods")),p.push("</tr>");let E=n.length===1?"file":"files";return p.push(`<tr class="totals-row"><td class="strong t-file-count">${O(n.length)} ${E}</td>`,Y(o.percent,o.covered,o.total,"line",!0)),l&&p.push(Y(l.percent,l.covered,l.total,"branch",!0)),d&&p.push(Y(d.percent,d.covered,d.total,"method",!0)),p.push("</tr></thead><tbody>"),p.join("")}function ms(e){let{filename:t,coverage:n,branchCoverage:s,methodCoverage:i}=e,c=ee(t),r=[`data-covered-lines="${n.covered_lines}"`,`data-relevant-lines="${n.total_lines}"`];s&&r.push(`data-covered-branches="${n.covered_branches||0}"`,`data-total-branches="${n.total_branches||0}"`),i&&r.push(`data-covered-methods="${n.covered_methods||0}"`,`data-total-methods="${n.total_methods||0}"`);let o=[`<tr class="t-file" ${r.join(" ")}>`,`<td class="strong t-file__name"><a href="#${c}" class="src_link" title="${I(t)}">${I(t)}</a></td>`,Y(n.lines_covered_percent,n.covered_lines,n.total_lines,"line",!1)];if(s){let l=n.branches_covered_percent===void 0?100:n.branches_covered_percent;o.push(Y(l,n.covered_branches||0,n.total_branches||0,"branch",!1))}if(i){let l=n.methods_covered_percent===void 0?100:n.methods_covered_percent;o.push(Y(l,n.covered_methods||0,n.total_methods||0,"method",!1))}return o.push("</tr>"),o.join("")}function qe(e){let{filenames:t,allCoverage:n,branchCoverage:s,methodCoverage:i}=e,c=[hs(e)];for(let r of t){let o=n[r];o&&c.push(ms({filename:r,coverage:o,branchCoverage:s,methodCoverage:i}))}return c.push("</tbody></table></div></div>"),c.join("")}function bs(e){let{lineIndex:t,lineCov:n,branchesReport:s,missedMethodLines:i,branchCoverage:c,methodCoverage:r}=e,o=t+1;if(n==="ignored")return"skipped";if(c){let l=s[o];if(l&&l.some(([,d])=>d===0))return"missed-branch"}return r&&i.has(o)?"missed-method":n===null?"never":n===0?"missed":"covered"}function _s(e){let t={};if(!e)return t;for(let{coverage:n,report_line:s,type:i}of e){if(n==="ignored")continue;(t[s]||(t[s]=[])).push([i,n])}return t}function Es(e){let t=new Set;if(!e)return t;for(let n of e)if(n.coverage===0&&n.start_line&&n.end_line)for(let s=n.start_line;s<=n.end_line;s++)t.add(s);return t}function vs(e){let{index:t,source:n,lineCov:s,status:i,branchCoverage:c,lineBranches:r}=e,o=t+1,l=s!==null&&s!=="ignored"?` data-hits="${s}"`:"",d=[`<li class="${i}"${l} data-linenumber="${o}">`];if(i==="covered"||s!==null&&s!=="ignored"&&s!==0?d.push(`<span class="hits" data-content="${s}"></span>`):s==="ignored"&&d.push('<span class="hits" data-content="skipped"></span>'),c&&r)for(let[p,E]of r){let w=I(p);d.push(`<span class="hits" data-content="${w}: ${E}" title="${w} branch hit ${E} times"></span>`)}return d.push(`<code class="ruby">${I(n)}</code></li>`),d.join("")}function Bt(e,t,n,s){let i=ee(e),c=t.covered_lines,r=t.total_lines,o=n&&t.covered_branches||0,l=n&&t.total_branches||0,d=s&&t.covered_methods||0,p=s&&t.total_methods||0,E=(t.methods||[]).filter(C=>C.coverage===0),w=s&&E.length>0,T=_s(t.branches),H=Es(t.methods),A=[`<div class="source_table" id="${i}">`,'<div class="header">',`<h2>${I(e)}</h2>`,Dt({coveredLines:c,totalLines:r,coveredBranches:o,totalBranches:l,coveredMethods:d,totalMethods:p,branchCoverage:n,methodCoverage:s,showMethodToggle:w})];w&&A.push('<div class="t-missed-method-list" style="display: none"><ul>',E.map(C=>`<li><tt>${I(C.name)}</tt></li>`).join(""),"</ul></div>"),A.push("</div>","<pre><ol>");for(let C=0;C<t.source.length;C++){let oe=t.lines[C],fe=bs({lineIndex:C,lineCov:oe,branchesReport:T,missedMethodLines:H,branchCoverage:n,methodCoverage:s});A.push(vs({index:C,source:t.source[C],lineCov:oe,status:fe,branchCoverage:n,lineBranches:n?T[C+1]:void 0}))}return A.push("</ol></pre></div>"),A.join("")}Ue.registerLanguage("ruby",kt);var se=null;function Pt(e){let t=e.meta,n=t.branch_coverage,s=t.method_coverage;document.title=`Code coverage for ${t.project_name}`;let i=Object.keys(e.coverage),c=e.total.lines.total>0?e.total.lines.percent:100,r=document.createElement("link");r.rel="icon",r.type="image/png",r.href=`favicon_${F(c)}.png`,document.head.appendChild(r),n&&document.body.setAttribute("data-branch-coverage","true");let o=document.getElementById("content"),l=[qe({title:"All Files",filenames:i,stats:e.total,allCoverage:e.coverage,branchCoverage:n,methodCoverage:s})];for(let H of Object.keys(e.groups)){let A=e.groups[H];l.push(qe({title:H,filenames:A.files||[],stats:A,allCoverage:e.coverage,branchCoverage:n,methodCoverage:s}))}o.innerHTML=l.join("");let d={};for(let H of i)d[ee(H)]=H;se={idToFilename:d,coverage:e.coverage,branchCoverage:n,methodCoverage:s};let p=new Date(t.timestamp),E=document.getElementById("footer");E.innerHTML=`Generated <abbr class="timeago" title="${p.toISOString()}">${p.toISOString()}</abbr> by <a href="https://github.com/simplecov-ruby/simplecov">simplecov</a> v${I(t.simplecov_version)} using ${I(t.command_name)}`;let w=document.getElementById("source-legend"),T='<span class="source-legend__item"><span class="source-legend__swatch source-legend__swatch--covered"></span>Covered</span><span class="source-legend__item"><span class="source-legend__swatch source-legend__swatch--skipped"></span>Skipped</span><span class="source-legend__item"><span class="source-legend__swatch source-legend__swatch--missed"></span>Missed line</span>';n&&(T+='<span class="source-legend__item"><span class="source-legend__swatch source-legend__swatch--missed-branch"></span>Missed branch</span>'),s&&(T+='<span class="source-legend__item"><span class="source-legend__swatch source-legend__swatch--missed-method"></span>Missed method</span>'),w.innerHTML=T}function Ft(e){let t=document.getElementById(e);if(t)return t;if(!se)return null;let n=se.idToFilename[e];if(!n)return null;let s=Bt(n,se.coverage[n],se.branchCoverage,se.methodCoverage),i=document.querySelector(".source_files"),c=document.createElement("div");c.innerHTML=s;let r=c.firstElementChild;return i.appendChild(r),_("pre code",r).forEach(o=>Ue.highlightElement(o)),r}var Ut={};function ys(e,t){var s;return(s=Array.from(e.children).filter(i=>i.style.display!=="none")[t])!=null?s:null}function Ms(e){if(!e)return"";let t=e.getAttribute("data-order");if(t!==null)return Number.parseFloat(t);let n=(e.textContent||"").trim(),s=Number.parseFloat(n);return Number.isNaN(s)?n.toLowerCase():s}function ws(e,t){let n=e.id||e.getAttribute("data-sort-id")||"default",s=Ut[n],i=s&&s.colIndex===t&&s.direction==="asc"?"desc":"asc";Ut[n]={colIndex:t,direction:i};let c=e.querySelector("tbody"),r=Array.from(c.querySelectorAll("tr.t-file")).map(d=>({row:d,value:Ms(ys(d,t))}));r.sort((d,p)=>{let E=d.value,w=p.value,T;return typeof E=="number"&&typeof w=="number"?T=E-w:T=String(E).localeCompare(String(w)),i==="asc"?T:-T});let o=document.createDocumentFragment();r.forEach(({row:d})=>o.appendChild(d)),c.appendChild(o);let l=0;_("thead tr:first-child th",e).forEach(d=>{let p=Number.parseInt(d.getAttribute("colspan")||"1",10);d.classList.remove("sorting_asc","sorting_desc","sorting");let E=t>=l&&t<l+p;d.classList.add(E?i==="asc"?"sorting_asc":"sorting_desc":"sorting"),l+=p})}function Ts(e,t){let n=0;for(let s of _("thead tr:first-child th",e)){let i=Number.parseInt(s.getAttribute("colspan")||"1",10);if(s===t)return n+i-1;n+=i}return n}function Wt(){_("table.file_list").forEach(e=>{_("thead tr:first-child th",e).forEach(t=>{t.classList.add("sorting"),t.style.cursor="pointer",t.addEventListener("click",()=>ws(e,Ts(e,t)))})})}var ue=null;function we(){ue=null}function jt(){if(ue)return ue;let e=_(".file_list_container").filter(t=>t.style.display!=="none");return e.length?(ue=_("tbody tr.t-file",e[0]).filter(t=>t.style.display!=="none"),ue):[]}var Ss=240,Ls=160;function qt(e,t){let n=t+"px";e.forEach(s=>{let i=s.style;i.width=n,i.minWidth=n,i.maxWidth=n})}function Ge(){_(".file_list_container").forEach(e=>{if(e.style.display==="none"||e.offsetWidth===0)return;let t=R("table.file_list",e);if(!t)return;let n=_(".bar-sizer",t);if(n.length===0)return;let s=t.closest(".file_list--responsive");if(!s)return;s.style.visibility="hidden";let i=Ls,c=Ss;for(;i<c;){let r=Math.ceil((i+c)/2);qt(n,r),t.offsetWidth,t.scrollWidth<=s.clientWidth?i=r:c=r-1}qt(n,i),s.style.visibility=""})}var ze=0;function ie(){ze||(ze=requestAnimationFrame(()=>{ze=0,Ge()}))}var Te={line:{covered:"coveredLines",total:"relevantLines"},branch:{covered:"coveredBranches",total:"totalBranches"},method:{covered:"coveredMethods",total:"totalMethods"}};function zt(e){let t=_("tbody tr.t-file",e).filter(c=>c.style.display!=="none");function n(c){return t.reduce((r,o)=>r+(Number.parseInt(o.dataset[c]||"0",10)||0),0)}let s=R(".t-file-count",e),i=Number.parseInt(e.getAttribute("data-total-files")||"0",10);if(s){let c=t.length===1?" file":" files";s.textContent=t.length===i?O(i)+c:O(t.length)+"/"+O(i)+c}for(let c of Object.keys(Te)){let r=Te[c],o=`.t-totals__${c}`;R(o+"-pct",e)&&xs(e,o,n(r.covered),n(r.total))}}function xs(e,t,n,s){let i=R(t+"-pct",e),c=R(t+"-num",e),r=R(t+"-den",e);if(s===0){i&&(i.innerHTML="",i.classList.remove("green","yellow","red")),c&&(c.textContent=""),r&&(r.textContent="");return}let o=n*100/s,l=F(o);i&&(i.innerHTML=`<div class="coverage-cell">${je(o)}<span class="coverage-pct">${j(o)}%</span></div>`,i.classList.remove("green","yellow","red"),i.classList.add(l)),c&&(c.textContent=O(n)+"/"),r&&(r.textContent=O(s))}var As={gt:(e,t)=>e>t,gte:(e,t)=>e>=t,eq:(e,t)=>e===t,lte:(e,t)=>e<=t,lt:(e,t)=>e<t};function Cs(e,t,n){let s=As[e];return s?s(t,n):!0}function Rs(e){let t=[];for(let n of _(".col-filter__value",e)){let s=n;if(!s.value)continue;let i=Number.parseFloat(s.value);if(Number.isNaN(i))continue;let c=s.dataset.type||"",r=R(`.col-filter__op[data-type="${c}"]`,e),o=r?r.value:"",l=Te[c];o&&l&&t.push({attrs:l,op:o,threshold:i})}return t}function Gt(e){let t=R("table.file_list",e);if(!t)return;let n=R(".col-filter--name",e),s=n?n.value.trim().toLowerCase():"",i=Rs(e);_("tbody tr.t-file",t).forEach(c=>{let r=c,o=(c.children[0].textContent||"").toLowerCase(),l=(!s||o.includes(s))&&i.every(d=>{let p=Number.parseInt(r.dataset[d.attrs.covered]||"0",10)||0,E=Number.parseInt(r.dataset[d.attrs.total]||"0",10)||0,w=E>0?p*100/E:100;return Cs(d.op,w,d.threshold)});r.style.display=l?"":"none"}),we(),zt(e),ie()}function Ke(e){let t=Number.parseFloat(e.value),n=e.closest(".col-filter__coverage"),s=n?n.querySelector(".col-filter__op"):null;if(!s)return;let i=s.querySelector('option[value="gt"]'),c=s.querySelector('option[value="lt"]');if(i&&(i.disabled=t>=100),c&&(c.disabled=t<=0),s.selectedOptions[0]&&s.selectedOptions[0].disabled){let r=s.querySelector("option:not(:disabled)");r&&(s.value=r.value)}}function Kt(){_(".col-filter__value").forEach(e=>Ke(e)),_(".col-filter--name, .col-filter__op, .col-filter__value, .col-filter__coverage").forEach(e=>{e.addEventListener("click",t=>t.stopPropagation())}),P(document,"input",".col-filter--name, .col-filter__op, .col-filter__value",function(){this.classList.contains("col-filter__value")&&Ke(this),Gt(this.closest(".file_list_container"))}),P(document,"change",".col-filter__op, .col-filter__value",function(){this.classList.contains("col-filter__value")&&Ke(this),Gt(this.closest(".file_list_container"))})}var $=null;function Xe(){return $!==null}function re(e){$&&$.classList.remove("keyboard-focus"),$=e,$&&($.classList.add("keyboard-focus"),$.scrollIntoView({block:"nearest"}))}function Ze(e){let t=jt();if(!t.length)return;if(!$||t.indexOf($)===-1){re(e===1?t[0]:t[t.length-1]);return}let n=t.indexOf($)+e;n>=0&&n<t.length&&re(t[n])}function Xt(){if(!$)return;let e=$.querySelector("a.src_link");e&&(window.location.hash=e.getAttribute("href").substring(1))}var q,Q,Ve,de=null,Se="";function Ye(){return q.open}function Qe(){return Q}function Vt(){if(!de)return;Se&&(de.insertAdjacentHTML("afterbegin",Se),Se="");let e=document.querySelector(".source_files");e&&e.appendChild(de),de=null}function Ns(e,t){Vt();let n=Ft(e);if(!n)return;let s=n.querySelector(".header");if(s&&(Se=s.outerHTML,Ve.innerHTML=s.innerHTML,s.remove()),de=n,Q.appendChild(n),q.open||q.showModal(),document.documentElement.style.overflow="hidden",Q.focus(),t){let i=Q.querySelector('li[data-linenumber="'+t+'"]');i&&(Q.scrollTop=i.offsetTop)}}function Zt(e){if(re(null),we(),q.open&&(Vt(),q.close(),Q.innerHTML="",Ve.innerHTML="",document.documentElement.style.overflow=""),e){let n=document.querySelector(".group_tabs a."+e);if(n){_(".group_tabs li").forEach(i=>i.classList.remove("active")),n.parentElement.classList.add("active"),_(".file_list_container").forEach(i=>i.style.display="none");let s=document.getElementById(e);s&&(s.style.display="")}}let t=document.getElementById("wrapper");t&&!t.classList.contains("hide")&&ie()}function xe(){let e=window.location.hash.substring(1);if(!e){let t=document.querySelector(".group_tabs a");t&&Zt(t.getAttribute("href").replace("#",""));return}if(e.charAt(0)==="_")Zt(e.substring(1));else{let t=e.split("-L");if(!document.querySelector(".group_tabs li.active")){let n=document.querySelector(".group_tabs li");n&&n.classList.add("active")}Ns(t[0],t[1])}}function Le(){let e=document.querySelector(".group_tabs li.active a");e&&(window.location.hash=e.getAttribute("href").replace("#","#_"))}function Yt(){q=document.getElementById("source-dialog"),Q=document.getElementById("source-dialog-body"),Ve=document.getElementById("source-dialog-title"),q.querySelector(".source-dialog__close").addEventListener("click",Le),q.addEventListener("click",e=>{e.target===q&&Le()})}function Os(){return _(".source-dialog .source_table li.missed, .source-dialog .source_table li.missed-branch, .source-dialog .source_table li.missed-method")}function Je(e){let t=Os();if(!t.length)return;let n=Qe(),s=n.scrollTop+n.clientHeight/2,i=e===1?t.find(c=>c.offsetTop>s)||t[0]:t.findLast(c=>c.offsetTop<s-10)||t[t.length-1];n.scrollTop=i.offsetTop-n.clientHeight/3}function Qt(){P(document,"click",".t-missed-method-toggle",function(e){e.preventDefault();let t=this.closest(".header")||this.closest(".source-dialog__title")||this.closest(".source-dialog__header"),n=t?t.querySelector(".t-missed-method-list"):null;n&&(n.style.display=n.style.display==="none"?"":"none")}),P(document,"click","a.src_link",function(e){e.preventDefault(),window.location.hash=this.getAttribute("href").substring(1)}),P(document,"click","table.file_list tbody tr",function(e){if(e.target.closest("a"))return;let t=this.querySelector("a.src_link");t&&(window.location.hash=t.getAttribute("href").substring(1))}),P(document,"click",".source-dialog .source_table li[data-linenumber]",function(e){e.preventDefault(),Qe().scrollTop=this.offsetTop;let t=this.dataset.linenumber,n=window.location.hash.substring(1).replace(/-L.*/,"");window.location.replace(window.location.href.replace(/#.*/,"#"+n+"-L"+t))}),window.addEventListener("hashchange",xe)}var Jt="simplecov-dark-mode";function Is(){try{return localStorage.getItem(Jt)}catch(e){return null}}function $s(e){try{localStorage.setItem(Jt,e)}catch(t){}}function en(){let e=document.getElementById("dark-mode-toggle");if(!e)return;let t=document.documentElement;function n(){return t.classList.contains("dark-mode")||!t.classList.contains("light-mode")&&window.matchMedia("(prefers-color-scheme: dark)").matches}function s(){e.textContent=n()?"\u2600\uFE0F Light":"\u{1F319} Dark"}s(),e.addEventListener("click",()=>{let i=n();t.classList.toggle("light-mode",i),t.classList.toggle("dark-mode",!i),$s(i?"light":"dark"),s()}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{Is()||s()})}function Hs(){let e=_(".file_list_container").filter(n=>n.style.display!=="none"),t=e.length?R(".col-filter--name",e[0]):null;t&&t.focus()}function ks(e,t){Ye()?(e.preventDefault(),Le()):t?e.target.blur():Xe()&&re(null)}function Ds(e){e.key==="n"&&!e.shiftKey&&(e.preventDefault(),Je(1)),(e.key==="N"||e.key==="n"&&e.shiftKey||e.key==="p")&&(e.preventDefault(),Je(-1))}function Bs(e){e.key==="j"&&(e.preventDefault(),Ze(1)),e.key==="k"&&(e.preventDefault(),Ze(-1)),e.key==="Enter"&&Xe()&&(e.preventDefault(),Xt())}function tn(e){let t=e.target.matches("input, select, textarea");e.key==="/"&&!t?(e.preventDefault(),Hs()):e.key==="Escape"?ks(e,t):t||(Ye()?Ds(e):Bs(e))}function nn(){let e=1/0;_("abbr.timeago").forEach(t=>{let n=new Date(t.getAttribute("title")||"");Number.isNaN(n.getTime())||(t.textContent=dt(n),e=Math.min(e,ft(n)))}),e<1/0&&setTimeout(nn,e)}function Ps(){_(".file_list_container").forEach(e=>e.style.display="none"),_(".file_list_container").forEach(e=>{let t=e.id,n=e.querySelector(".group_name"),s=e.querySelector(".covered_percent"),i=document.createElement("li");i.setAttribute("role","tab");let c=document.createElement("a");c.href="#"+t,c.className=t,c.innerHTML=(n?n.innerHTML:"")+" ("+(s?s.innerHTML:"")+")",i.appendChild(c),document.querySelector(".group_tabs").appendChild(i)}),P(document.querySelector(".group_tabs"),"click","a",function(e){e.preventDefault(),window.location.hash=this.getAttribute("href").replace("#","#_")})}function Fs(e){e&&(e.style.transition="opacity 0.3s",e.style.opacity="0",setTimeout(()=>{e.style.display="none"},300));let t=document.getElementById("wrapper");t&&t.classList.remove("hide"),Ge()}function Us(){return J(this,null,function*(){let e=window.SIMPLECOV_DATA,t=document.getElementById("loading");t&&(t.style.display=""),yield pt(Object.keys(e.coverage)),Pt(e),nn(),en(),Wt(),Kt(),document.addEventListener("keydown",tn),Yt(),Qt(),Ps(),window.addEventListener("resize",ie),xe(),Fs(t)})}document.addEventListener("DOMContentLoaded",Us);})();
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Code Coverage</title>
7
+ <script src="application.js" defer></script>
8
+ <link href="application.css" rel="stylesheet">
9
+ <script>
10
+ // Apply the saved dark/light preference before paint to avoid a flash.
11
+ try {
12
+ const pref = localStorage.getItem('simplecov-dark-mode');
13
+ if (pref === 'dark' || pref === 'light') {
14
+ document.documentElement.classList.add(`${pref}-mode`);
15
+ }
16
+ } catch (_error) {
17
+ // localStorage can be unavailable in locked-down browser contexts.
18
+ }
19
+ </script>
20
+ </head>
21
+
22
+ <body>
23
+ <div id="loading" style="display: none">
24
+ <div id="loading-inner">
25
+ <div id="loading-bar-track">
26
+ <div id="loading-bar-fill"></div>
27
+ </div>
28
+ <div id="loading-text">Loading...</div>
29
+ </div>
30
+ </div>
31
+
32
+ <div id="wrapper" class="hide">
33
+ <div class="tab-bar">
34
+ <ul class="group_tabs" role="tablist"></ul>
35
+ <button id="dark-mode-toggle" aria-label="Toggle dark mode"></button>
36
+ </div>
37
+
38
+ <div id="content"></div>
39
+
40
+ <div id="footer"></div>
41
+
42
+ <div class="source_files" style="display:none"></div>
43
+ </div>
44
+
45
+ <dialog id="source-dialog" class="source-dialog">
46
+ <div class="source-dialog__header">
47
+ <div class="source-dialog__title" id="source-dialog-title"></div>
48
+ <div class="source-legend" id="source-legend"></div>
49
+ <button class="source-dialog__close" aria-label="Close" title="Close">&times;</button>
50
+ </div>
51
+ <div class="source-dialog__body" id="source-dialog-body" tabindex="0"></div>
52
+ </dialog>
53
+
54
+ <script src="coverage_data.js" defer></script>
55
+ </body>
56
+ </html>
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+ require_relative "base"
6
+ require_relative "json_formatter"
7
+
8
+ module SimpleCov
9
+ module Formatter
10
+ # Generates an HTML coverage report by writing a coverage_data.js file
11
+ # alongside pre-compiled static assets (index.html, application.js/css).
12
+ # Uses JSONFormatter.build_hash to serialize the result, then writes both
13
+ # coverage.json and coverage_data.js from the same in-memory hash.
14
+ class HTMLFormatter < Base
15
+ DATA_FILENAME = "coverage_data.js"
16
+
17
+ def format(result)
18
+ # `coverage_data.js` feeds the client-side viewer, which renders
19
+ # source from the embedded array — it always needs `source`,
20
+ # regardless of `SimpleCov.source_in_json`. The side-file
21
+ # `coverage.json` honors the setting so downstream tools that
22
+ # read source from disk can opt into a smaller payload. When
23
+ # the setting is at its default (true), the two files share a
24
+ # single serialization.
25
+ FileUtils.mkdir_p(output_path)
26
+ viewer_json = JSON.pretty_generate(JSONFormatter.build_hash(result, include_source: true))
27
+ coverage_json = SimpleCov.source_in_json ? viewer_json : JSON.pretty_generate(JSONFormatter.build_hash(result))
28
+
29
+ atomic_write(File.join(output_path, JSONFormatter::FILENAME), coverage_json)
30
+ atomic_write(File.join(output_path, DATA_FILENAME), "window.SIMPLECOV_DATA = #{viewer_json};\n")
31
+
32
+ copy_static_assets
33
+ # stderr, not stdout: this is a status message, not the program's
34
+ # output. Keeps the line out of pipelines like `rspec -f json`.
35
+ warn output_message(result) unless @silent
36
+ end
37
+
38
+ # Generate HTML from a pre-existing coverage.json file without
39
+ # needing a live SimpleCov::Result or even a running test suite.
40
+ def format_from_json(json_path, output_dir)
41
+ FileUtils.mkdir_p(output_dir)
42
+ json = File.read(json_path)
43
+ atomic_write(File.join(output_dir, DATA_FILENAME), "window.SIMPLECOV_DATA = #{json};\n")
44
+ copy_static_assets(output_dir)
45
+ end
46
+
47
+ private
48
+
49
+ def entry_point_filename
50
+ "index.html"
51
+ end
52
+
53
+ def copy_static_assets(dest_dir = output_path)
54
+ Dir[File.join(public_dir, "*")].each do |src|
55
+ atomic_write(File.join(dest_dir, File.basename(src)), File.binread(src))
56
+ end
57
+ end
58
+
59
+ # Write `content` at `dest` via a uniquely-named temp file in the
60
+ # same directory, then `File.rename` onto the final path. rename is
61
+ # atomic and overwrite-safe, so:
62
+ # - parallel writers can't race on an unlink-then-write window, and
63
+ # - read-only existing files (e.g. assets shipped at 0444 from
64
+ # /nix/store) are replaced cleanly instead of triggering EACCES
65
+ # from opening the existing path for writing.
66
+ def atomic_write(dest, content)
67
+ temp = "#{dest}.#{Process.pid}.#{rand(2**32).to_s(36)}"
68
+ File.binwrite(temp, content)
69
+ File.rename(temp, dest)
70
+ ensure
71
+ FileUtils.rm_f(temp)
72
+ end
73
+
74
+ def public_dir
75
+ File.join(__dir__, "html_formatter/public/")
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Formatter
5
+ class JSONFormatter
6
+ # Translates the threshold violations reported by
7
+ # `SimpleCov::CoverageViolations` into the `:errors` section of
8
+ # coverage.json. Each violation is keyed by criterion
9
+ # (`:lines` / `:branches` / `:methods`) so consumers can render
10
+ # per-criterion messages without re-deriving them.
11
+ class ErrorsFormatter
12
+ CRITERION_KEYS = {line: :lines, branch: :branches, method: :methods}.freeze
13
+ private_constant :CRITERION_KEYS
14
+
15
+ def initialize(result)
16
+ @result = result
17
+ @errors = {}
18
+ end
19
+
20
+ def call
21
+ format_minimum_overall
22
+ format_minimum_by_file
23
+ format_minimum_by_group
24
+ format_maximum_overall
25
+ format_maximum_drop
26
+ @errors
27
+ end
28
+
29
+ private
30
+
31
+ def format_minimum_overall
32
+ SimpleCov::CoverageViolations.minimum_overall(@result, SimpleCov.minimum_coverage).each do |violation|
33
+ bucket(:minimum_coverage)[key_for(violation)] = expected_actual(violation)
34
+ end
35
+ end
36
+
37
+ def format_minimum_by_file
38
+ violations = SimpleCov::CoverageViolations.minimum_by_file(
39
+ @result, SimpleCov.minimum_coverage_by_file, SimpleCov.minimum_coverage_by_file_overrides
40
+ )
41
+ violations.each { |violation| record_by_file(violation) }
42
+ end
43
+
44
+ def record_by_file(violation)
45
+ file_bucket = bucket(:minimum_coverage_by_file)[violation.fetch(:project_filename)] ||= {}
46
+ file_bucket[key_for(violation)] = expected_actual(violation)
47
+ end
48
+
49
+ def format_minimum_by_group
50
+ violations = SimpleCov::CoverageViolations.minimum_by_group(@result, SimpleCov.minimum_coverage_by_group)
51
+ violations.each do |violation|
52
+ group_bucket = bucket(:minimum_coverage_by_group)[violation.fetch(:group_name)] ||= {}
53
+ group_bucket[key_for(violation)] = expected_actual(violation)
54
+ end
55
+ end
56
+
57
+ def format_maximum_overall
58
+ SimpleCov::CoverageViolations.maximum_overall(@result, SimpleCov.maximum_coverage).each do |violation|
59
+ bucket(:maximum_coverage)[key_for(violation)] = expected_actual(violation)
60
+ end
61
+ end
62
+
63
+ def format_maximum_drop
64
+ SimpleCov::CoverageViolations.maximum_drop(@result, SimpleCov.maximum_coverage_drop).each do |violation|
65
+ bucket(:maximum_coverage_drop)[key_for(violation)] =
66
+ {maximum: violation.fetch(:maximum), actual: violation.fetch(:actual)}
67
+ end
68
+ end
69
+
70
+ def bucket(name)
71
+ @errors[name] ||= {}
72
+ end
73
+
74
+ def key_for(violation)
75
+ CRITERION_KEYS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion)))
76
+ end
77
+
78
+ def expected_actual(violation)
79
+ {expected: violation.fetch(:expected), actual: violation.fetch(:actual)}
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "time"
5
+ require_relative "errors_formatter"
6
+ require_relative "source_file_formatter"
7
+
8
+ module SimpleCov
9
+ module Formatter
10
+ class JSONFormatter
11
+ # Builds the hash that JSONFormatter serializes to coverage.json:
12
+ # meta, per-file coverage data, group totals, and aggregate stats.
13
+ class ResultHashFormatter
14
+ # Bump SCHEMA_VERSION (and SCHEMA_URL) when the JSON shape
15
+ # changes. Additive changes bump the minor segment, removals or
16
+ # shape changes bump the major segment. The versioned file at
17
+ # schemas/coverage-vX.Y.schema.json is the canonical artifact
18
+ # consumers should pin to, schemas/coverage.schema.json is a
19
+ # convenience alias that always tracks the latest. See the
20
+ # `coverage.json` schema section of the README for the rationale.
21
+ SCHEMA_VERSION = "1.0"
22
+ SCHEMA_URL = "https://raw.githubusercontent.com/simplecov-ruby/simplecov/main/schemas/coverage-v#{SCHEMA_VERSION}.schema.json".freeze
23
+ private_constant :SCHEMA_VERSION, :SCHEMA_URL
24
+
25
+ def initialize(result, include_source: true)
26
+ @result = result
27
+ @include_source = include_source
28
+ end
29
+
30
+ def format
31
+ {
32
+ :$schema => SCHEMA_URL,
33
+ :meta => format_meta,
34
+ :total => format_coverage_statistics(@result.coverage_statistics),
35
+ :coverage => format_files,
36
+ :groups => format_groups,
37
+ :errors => ErrorsFormatter.new(@result).call
38
+ }
39
+ end
40
+
41
+ private
42
+
43
+ def format_files
44
+ @result.files.to_h do |source_file|
45
+ [source_file.project_filename, SourceFileFormatter.new(source_file, include_source: @include_source).call]
46
+ end
47
+ end
48
+
49
+ def format_groups
50
+ @result.groups.to_h do |name, file_list|
51
+ stats = format_coverage_statistics(file_list.coverage_statistics)
52
+ [name, stats.merge(files: file_list.map(&:project_filename))]
53
+ end
54
+ end
55
+
56
+ def format_meta
57
+ {
58
+ schema_version: SCHEMA_VERSION,
59
+ simplecov_version: SimpleCov::VERSION,
60
+ command_name: @result.command_name,
61
+ project_name: SimpleCov.project_name,
62
+ timestamp: @result.created_at.iso8601(3),
63
+ root: SimpleCov.root,
64
+ commit: git_commit
65
+ }.merge!(coverage_flags)
66
+ end
67
+
68
+ # Full git commit SHA of `SimpleCov.root`'s HEAD, or nil when the
69
+ # project isn't a git checkout or git isn't on PATH. Recorded so tools
70
+ # can recover the exact source a report was generated against, which
71
+ # matters most when `source_in_json false` drops the source text from
72
+ # coverage.json. stderr is captured (not forwarded) so a non-git project
73
+ # doesn't print git's diagnostics to the build.
74
+ def git_commit
75
+ output, status = Open3.capture2e("git", "-C", SimpleCov.root.to_s, "rev-parse", "HEAD")
76
+ status.success? ? output.strip : nil
77
+ rescue StandardError
78
+ nil
79
+ end
80
+
81
+ def coverage_flags
82
+ {
83
+ line_coverage: line_coverage_enabled?,
84
+ branch_coverage: SimpleCov.branch_coverage?,
85
+ method_coverage: SimpleCov.method_coverage?
86
+ }
87
+ end
88
+
89
+ # Mirrors SourceFileFormatter's predicate so meta.line_coverage
90
+ # tracks exactly which configurations cause the formatter to
91
+ # emit line stats.
92
+ def line_coverage_enabled?
93
+ SimpleCov.coverage_criterion_enabled?(:line) || SimpleCov.coverage_criterion_enabled?(:oneshot_line)
94
+ end
95
+
96
+ def format_coverage_statistics(statistics)
97
+ result = {}
98
+ result[:lines] = format_line_statistic(statistics[:line]) if statistics[:line]
99
+ result[:branches] = format_single_statistic(statistics[:branch]) if statistics[:branch]
100
+ result[:methods] = format_single_statistic(statistics[:method]) if statistics[:method]
101
+ result
102
+ end
103
+
104
+ def format_line_statistic(stat)
105
+ {
106
+ covered: stat.covered,
107
+ missed: stat.missed,
108
+ omitted: stat.omitted,
109
+ total: stat.total,
110
+ percent: stat.percent,
111
+ strength: stat.strength
112
+ }
113
+ end
114
+
115
+ def format_single_statistic(stat)
116
+ {
117
+ covered: stat.covered,
118
+ missed: stat.missed,
119
+ total: stat.total,
120
+ percent: stat.percent,
121
+ strength: stat.strength
122
+ }
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end