Count each statement once in coverage however often it lowers (#1366)

This commit is contained in:
Chris Tate
2026-10-18 23:38:30 -05:00
committed by GitHub
4 changed files with 33 additions and 18 deletions
@@ -4598,8 +4598,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[]
continue;
}
const f = item.f;
lowerer.stats.statementsTotal++;
lowerer.bumpFileStat(locOf(f.initializer).file, "total");
lowerer.countStatement(f.initializer, locOf(f.initializer).file);
const diagsBefore = lowerer.diags.length;
const sinkMark = lowerer.diagSinkMark();
try {
@@ -4635,8 +4634,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[]
out.push({ kind: "assign", localId: f.globalId, value, loc: locOf(f.initializer) });
} catch (e) {
if (!(e instanceof PoisonError)) throw e;
lowerer.stats.statementsFailed++;
lowerer.bumpFileStat(locOf(f.initializer).file, "failed");
lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file);
if (isJsSourceFile(f.initializer.getSourceFile())) {
const fence =
lowerer.diagSink === null
@@ -7764,8 +7762,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL
});
}
if (!f.initializer) continue;
lowerer.stats.statementsTotal++;
lowerer.bumpFileStat(locOf(f.initializer).file, "total");
lowerer.countStatement(f.initializer, locOf(f.initializer).file);
const diagsBefore = lowerer.diags.length;
const sinkMark = lowerer.diagSinkMark();
try {
@@ -7780,8 +7777,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL
});
} catch (e) {
if (!(e instanceof PoisonError)) throw e;
lowerer.stats.statementsFailed++;
lowerer.bumpFileStat(locOf(f.initializer).file, "failed");
lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file);
if (isJsSourceFile(f.initializer.getSourceFile())) {
const fence =
lowerer.diagSink === null
@@ -689,10 +689,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir
entry.index = i;
if (unreachable && skipsWhenUnreachable(stmt)) continue;
if (inFunction && isAbruptStatement(stmt)) unreachable = true;
if (!lowerer.suppressStats) {
lowerer.stats.statementsTotal++;
if (sf !== undefined) lowerer.bumpFileStat(sf.fileName, "total");
}
if (!lowerer.suppressStats) lowerer.countStatement(stmt, sf?.fileName);
const diagsBefore = lowerer.diags.length;
const sinkMark = lowerer.diagSinkMark();
try {
@@ -757,10 +754,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir
} else if (!(e instanceof PoisonError)) {
throw e;
}
if (!lowerer.suppressStats) {
lowerer.stats.statementsFailed++;
if (sf !== undefined) lowerer.bumpFileStat(sf.fileName, "failed");
}
if (!lowerer.suppressStats) lowerer.countStatementFailed(stmt, sf?.fileName);
lowerer.noteBlockedBindings(stmt);
// The runtime-fence conversion (JS sources, direct diagnostics
// only — a capture wrapper in force means collection owns the
@@ -2925,6 +2925,32 @@ export class Lowerer {
* pass skips it (attribution describes the build). */
readonly statsByFile = new Map<string, { total: number; failed: number }>();
/** Statements and field initializers already in the coverage totals, and
* those already counted as failed. Speculative lowering can lower one
* function body several times: each receiver probe of
* `(function () { ... }).call(this)` lowers it. A source statement counts
* once however many times it lowers. */
private readonly countedStatements = new Set<ts.Node>();
private readonly failedStatements = new Set<ts.Node>();
/** Adds `node` to the coverage totals unless it is already counted (the
* caller checks suppressStats). */
countStatement(node: ts.Node, file: string | undefined): void {
if (this.countedStatements.has(node)) return;
this.countedStatements.add(node);
this.stats.statementsTotal++;
if (file !== undefined) this.bumpFileStat(file, "total");
}
/** Counts `node` as failed unless it already is (the caller checks
* suppressStats). */
countStatementFailed(node: ts.Node, file: string | undefined): void {
if (this.failedStatements.has(node)) return;
this.failedStatements.add(node);
this.stats.statementsFailed++;
if (file !== undefined) this.bumpFileStat(file, "failed");
}
/** Bumps the per-file attribution counter (no-op unless provenance is
* active and this is the emit/discovery lane — mirror the CALLER's
* suppressStats guard, this method only gates remainder). */
@@ -5,8 +5,7 @@
// and `typeof module.exports` compile; a statement with no static lowering
// (here `module.exports` passed as a value in the class base) compiles to
// its runtime fence instead of vanishing. It never runs, so the binary
// matches Node. Coverage counts that one fenced statement once: the class
// base's body is lowered once.
// matches Node.
const verbose = process.argv.length > 99;
(function () {
var root = this;