Skip to content

Commit 8dd8043

Browse files
committed
Harden HTML report rendering
Escape generated HTML without allocating DOM nodes, including attribute quotes. The previous DOM-based `escapeHTML` only handled `&`, `<`, and `>`, so a filename containing `"` or `'` could break out of a `title=` attribute. Preserve real 0% branch and method coverage values in the file-row renderer instead of treating them as falsy and substituting the 100% fallback used for disabled criteria. Add small `getThemePreference` / `setThemePreference` helpers around `localStorage` so the toggle click and the prefers-color-scheme listener match the head-script preflight's try/catch. localStorage can throw in Safari private mode, sandboxed iframes, and browsers with storage disabled, and the previous direct calls would crash dark-mode interaction in those contexts. Precompute sort keys before row reordering, avoid large spread appends, simplify filter parsing and class updates, harden the dark-mode preflight, and rebuild the public formatter assets. CHANGELOG entries under Bugfixes document the attribute-escape and 0%-display fixes.
1 parent 3de2abd commit 8dd8043

5 files changed

Lines changed: 175 additions & 122 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ Unreleased
5454
* Added `:eval_generated` tokens to `SimpleCov.ignore_branches` and the new `SimpleCov.ignore_methods` so projects using macros like Rails' `delegate` (or any pattern that calls `module_eval(body, __FILE__, __LINE__)`) can drop the synthetic branch and method entries those macros inject. Ruby's `Coverage` attributes eval'd code to the caller's `__FILE__` / `__LINE__`, so a `delegate :foo, to: :bar` line surfaces as if it had a `def foo` and an `if` branch right there. Detection uses Prism to walk the static source and treats any Coverage entry whose start_line lacks a real `def` keyword (for methods) or branch construct (for branches) as eval-generated. Opt in with `ignore_methods :eval_generated` and / or `ignore_branches :eval_generated`. Prism ships with Ruby 3.3+; on older Rubies `gem install prism` enables the filter, otherwise the setting is a no-op. See #1046.
5555
* Files added via `cover` / `track_files` that were never `require`'d during the run now contribute branch and method entries to the report, not just lines. Previously `SimulateCoverage` left those fields as empty hashes (because parsing source ourselves felt risky), which made unloaded files invisible to the branch and method denominators while their lines DID count — so a `cover "{app,lib}/**/*.rb"` glob over files without specs silently inflated branch% relative to line% (the OP's reproduction was via SonarQube, which surfaces the asymmetry more visibly than the SimpleCov HTML report). Branches and methods are now enumerated statically via `SimpleCov::StaticCoverageExtractor`, which uses Prism to walk the AST and emits Coverage-shaped tuples without loading the file. The shape matches what Ruby's own `Coverage` library reports for the same source: `:if` / `:case` / `:while` / `:until` constructs plus their `:then` / `:else` / `:when` / `:in` / `:body` arms, with the synthetic `:else` for case-without-explicit-else that the `ignore_branches :implicit_else` setting (see Enhancements) targets. Prism is bundled with Ruby 3.3+; on older Rubies `gem install prism` enables the fix, otherwise SimulateCoverage falls back to the previous "empty hashes" behavior. See #1059.
5656
* HTML report: two groups whose names share an alphanumeric suffix but differ only in a leading non-letter (e.g. `">100LOC"` / `"<10LOC"`, or any pair using different special characters) no longer render into the same DOM container. The JS that built HTML ids from group names stripped every non-letter prefix and then every remaining non-alphanumeric char, so both names sanitized to `"LOC"` and the second group silently replaced the first in the rendered tabs. The new encoding (`"g-" + each-non-id-char-as-hex`) preserves uniqueness across all input shapes. See #1038.
57+
* HTML report: filenames containing `"` or `'` characters are now escaped when rendered into `title="..."` attributes. The previous DOM-based `escapeHTML` only escaped `&`, `<`, and `>`, so a project with such filenames could break out of the attribute. The replacement encodes all five HTML-attribute-sensitive characters via a `replace` callback (also avoids allocating a DOM node per call).
58+
* HTML report: files with literally 0% branch or method coverage now display 0% instead of 100%. The per-file row rendering used `f.branches_covered_percent || 100.0` (and the method equivalent), which treated a real `0` as falsy and substituted the disabled-criterion fallback. The check now distinguishes "criterion disabled" (`undefined`) from "criterion measured zero" (`0`).
5759
* `SimpleCov::Result` now warns when it drops source files because their absolute paths aren't on the local filesystem, instead of silently producing an empty `0 / 0 (100.00%)` report. The most common trigger is `SimpleCov.collate` invoked from a machine or working directory different from where the individual resultsets were generated — when *every* entry is missing the warning explicitly names that case and points at the issue; when only some are missing the warning is quieter and lists up to five paths with a `(+N more)` suffix. See #980.
5860
* Files added via `track_files` that were never loaded now use the same line classification as loaded files. Previously, `SimulateCoverage` ran the file through `LinesClassifier`, which marks every non-blank, non-comment line as relevant — so a multi-line method chain `@x = a.foo.bar` reported 4 relevant lines for the unloaded copy and 2 for the loaded copy, throwing off per-file and overall percentages. `SimulateCoverage` now uses `Coverage.line_stub` (the same stub Ruby would have produced if the file were required), then overlays `# :nocov:` toggles and `# simplecov:disable line` directive ranges that the runtime doesn't know about. The two paths now agree on every shape: multi-line statements, `end` keywords, blank lines, and SimpleCov-specific exclusion comments. Some projects will see their `tracked_files` percentages shift as a result. See #654.
5961
* Fix the parent-process / subprocess race where a Rakefile (or Rails `Bundler.require`) caused `.simplecov` to auto-load `SimpleCov.start` in the rake parent, which then shelled out to a test runner subprocess; the subprocess wrote a correct report, then the parent's `at_exit` would clobber it with an empty 0% report. Three layers of defense now apply: (1) `.simplecov` is treated as configuration only and no longer starts tracking from the parent (see Deprecations); (2) `ResultMerger.store_result` merges incoming entries with same-`command_name` entries that were written after our `process_start_time` instead of overwriting them; (3) `SimpleCov.at_exit_behavior` defers entirely when our merged result is empty and `coverage/.last_run.json` is fresher than this process. See #581.

0 commit comments

Comments
 (0)