Skip to content

fix(coverage): expand multi-line statements to all lines#154

Merged
sohil-kshirsagar merged 3 commits intomainfrom
sohil/fix/coverage-multiline-statements
Apr 10, 2026
Merged

fix(coverage): expand multi-line statements to all lines#154
sohil-kshirsagar merged 3 commits intomainfrom
sohil/fix/coverage-multiline-statements

Conversation

@sohil-kshirsagar
Copy link
Copy Markdown
Contributor

Multi-line JavaScript statements (like res.json({...}) spanning lines 14-20) only showed coverage on their first line. The rest appeared as gaps in the coverage UI — neither covered nor uncovered.

Root cause

coverageProcessor.ts extracts line coverage from Istanbul's statementMap, which has start and end line/column pairs for each statement. The code only read stmtMap.start.line, ignoring stmtMap.end.line:

// Before — only first line
const line = String(stmtMap.start.line);
lines[line] = Math.max(lines[line] ?? 0, count);

Fix

Expand each statement to all lines in its range, and process statements largest-first so inner (more specific) statements override outer ones. This handles the key edge case: a try-catch block (covered, count=1) contains a catch body (uncovered, count=0) — the catch body must stay uncovered even though it's inside a covered range.

Extracted the logic into a standalone extractLineCoverage() function for testability.

Impact on coverage numbers

Tested on example-express-server:

  • Before: 81.2% (52/64 lines) — many lines invisible
  • After: 87.8% (144/164 lines) — correct, all lines tracked

The total coverable lines increased from 64 to 164 because continuation lines of multi-line statements are now counted. The percentage changed because both covered and coverable increased proportionally.

Review notes

  • Python SDK is not affected — coverage.py reports individual lines natively
  • TypeScript works correctly because the fix operates on Istanbul's output (post-source-map), not raw V8 data
  • The overlapping statement sort (largest-first) is the critical detail — without it, outer covered statements would incorrectly mark inner uncovered catch blocks as covered

V8 coverage uses byte offsets which ast-v8-to-istanbul converts to
Istanbul's statementMap with start/end line numbers. Previously we only
recorded the start line of each statement, so multi-line statements like
res.json({...}) spanning lines 14-20 only showed line 14 as covered.

Now we expand each statement to all lines in its range. Statements are
processed largest-first so inner (more specific) statements override
outer ones — e.g. a try-catch block (covered) contains a catch body
(uncovered), and the catch body correctly stays uncovered.

Extract extractLineCoverage() as a standalone testable function with
4 unit tests covering: multi-line expansion, single-line statements,
overlapping statement override, and empty input.

Before: 81.2% (52/64 lines) on example-express-server
After:  87.8% (144/164 lines) — correct count with full line tracking
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3c9d9ac. Configure here.

@sohil-kshirsagar sohil-kshirsagar marked this pull request as ready for review April 10, 2026 21:15
Copy link
Copy Markdown

@tusk-dev tusk-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 2 issues

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/core/coverageProcessor.ts">

<violation number="1" location="src/core/coverageProcessor.ts:89">
P1: Unconditional overwrite of per-line counts can mark covered lines as uncovered when multiple statements map to the same line.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Review bots correctly identified that unconditional last-write-wins
could mark covered lines as uncovered when two same-size statements
map to the same line (e.g. if(x) foo(); else bar();).

Now track the statement size per line: strictly smaller (inner)
statements override, same-size statements use Math.max so covered
wins. Add test for same-size overlap case.
@tusk-dev
Copy link
Copy Markdown

tusk-dev bot commented Apr 10, 2026

Tip

New to Tusk? Learn more here.

Unit Tests

Generated 4 tests - 4 passed

Commit tests View tests

Test Summary

  • extractLineCoverage - 4 ✓

Results

Tusk's tests all pass. The test suite validates the core logic of extractLineCoverage(), the new function that expands multi-line statements across all their lines and handles overlapping statements correctly. The tests confirm that the function handles incomplete coverage data gracefully, preserves execution counts across multi-line ranges, treats non-overlapping statements independently, and critically — ensures inner statements override outer ones when they overlap. This last scenario is the key edge case: a covered try-catch block containing an uncovered catch body must keep the catch body marked as uncovered, not inherit the outer block's coverage. That's exactly what the fix does, and Tusk's tests prove it works.


Code Review

Tusk Review: No issues found

View check history

Commit Unit Tests Created (UTC)
ca678ae 4 ✓, 0 ✗ · Tests Apr 10, 2026 9:15PM
0923b86 4 ✓, 0 ✗ · Tests Apr 10, 2026 9:22PM

Was Tusk helpful? Give feedback by reacting with 👍 or 👎

@sohil-kshirsagar sohil-kshirsagar merged commit 51bcffa into main Apr 10, 2026
20 checks passed
@sohil-kshirsagar sohil-kshirsagar deleted the sohil/fix/coverage-multiline-statements branch April 10, 2026 22:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants