What language is this for?
TypeScript
Which rule?
Not a rule violation — this is a false-positive in coverage condition counting (the uncovered_conditions / conditions_to_cover metric). The “Partially covered” indicator on a line uses the same condition counter, and that
counter inflates branch counts on ?? (nullish coalescing) expressions.
Why I believe it’s a false-positive
A single line containing a nullish coalescing (??) expression is reported as having more conditions than actually exist. Istanbul/Jest coverage shows 100% branch coverage on the line (all branches the language defines are
hit), but SonarQube reports e.g. “4 of 6 conditions covered” — leaving 2 phantom uncovered conditions that no test can possibly reach. This blocks the “Coverage on New Code ≥ 90%” quality gate on otherwise fully-tested code, and
the only fix is to rewrite the ?? as an explicit if/return — semantically identical but cosmetically uglier.
Which Sonar product
- SonarQube Server, version
2025.4.3(build113915) - Scanner CLI:
SonarScanner CLI 8.0.1.6346(running in CircleCI viasonar-scannerinpull_request_analysismode) - SonarJS plugin:
10.25.0.33900 - Not using SonarQube Cloud, not using SonarQube for IDE / Connected Mode
Toolchain producing the coverage report
- TypeScript:
4.8.4 - Jest:
28.1.3withts-jest 28.0.8(Istanbul coverage reporter, lcov output) - Cypress:
15.16.0with@jsdevtools/coverage-istanbul-loader(lcov output) - Both lcov files merged via
sonar.javascript.lcov.reportPaths=**/jest/lcov.info,**/cypress/lcov.info
Self-contained reproducer
// resolve.ts
export interface Item {
key: string;
}
export function resolveItem(items: Item[], key: string): Item {
// SonarQube reports "4 of 6 conditions covered" on the line below,
// even though both branches of `??` are exercised by the tests.
return items.find(item => item.key === key) ?? { key };
}
// resolve.spec.ts
import { resolveItem } from './resolve';
describe('resolveItem', () => {
it('returns the matching item when present', () => {
const match = { key: 'a' };
expect(resolveItem([match], 'a')).toBe(match);
});
it('returns a fallback when no item matches', () => {
expect(resolveItem([{ key: 'a' }], 'b')).toEqual({ key: 'b' });
});
it('returns a fallback when the list is empty', () => {
expect(resolveItem([], 'a')).toEqual({ key: 'a' });
});
});
Run jest --coverage. Istanbul’s text report shows 100% branches:
File | % Stmts | % Branch | % Funcs | % Lines
-------------|---------|----------|---------|--------
resolve.ts | 100 | 100 | 100 | 100
Istanbul’s lcov.info for the ?? line lists 4 BRDA records (2 for the cond-expr, 2 for the binary-expr), all with non-zero hit counts:
DA:7,3
BRDA:7,2,0,1
BRDA:7,2,1,2
BRDA:7,3,0,2
BRDA:7,3,1,1
BRF:4
BRH:4
end_of_record
After ingestion, SonarQube reports the line as partially covered (4 of 6 conditions) — 2 conditions that have no corresponding source-level branch, and that no test is capable of hitting.
Workaround
Rewriting the same logic without ?? clears the gate:
export function resolveItem(items: Item[], key: string): Item {
const match = items.find(item => item.key === key);
if (match) {
return match;
}
return { key };
}
The same three tests now produce 2 of 2 conditions covered in SonarQube, and the lcov BRDA records still show the same total coverage. The runtime semantics are identical.
Expected behavior
A line containing a ?? b should expose at most 2 conditions to SonarQube (matching the 2 control-flow branches the operator introduces — a is non-nullish vs a is nullish). The current behavior counts ~3 conditions per ??
operand, double-counting against istanbul’s BRDA records and reporting unreachable phantom conditions.