Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 17x 17x 17x 17x 16x 16x 15x 15x 14x 14x 13x 13x 12x 12x 1x 2x 2x 1x 2x 2x 1x 1x 7x 6x 6x | /**
* Grammar Coverage Listener
* Tracks which ANTLR grammar rules are executed during parsing
*
* This listener attaches to the parser and records:
* - Parser rules visited (e.g., program, expression, statement)
* - Lexer rules matched (e.g., IDENTIFIER, INTEGER_LITERAL)
*
* Used to identify dead grammar code and untested language constructs.
*/
import {
ErrorNode,
ParserRuleContext,
ParseTreeListener,
TerminalNode,
} from "antlr4ng";
import IGrammarCoverageReport from "./types/IGrammarCoverageReport";
import GrammarCoverageReportBuilder from "./types/GrammarCoverageReportBuilder";
class GrammarCoverageListener implements ParseTreeListener {
private readonly parserRuleVisits: Map<string, number> = new Map();
private readonly lexerRuleVisits: Map<string, number> = new Map();
private readonly parserRuleNames: string[];
private readonly lexerRuleNames: string[];
constructor(parserRuleNames: string[], lexerRuleNames: string[]) {
this.parserRuleNames = parserRuleNames;
this.lexerRuleNames = lexerRuleNames;
}
/**
* Called when entering every parser rule
*/
enterEveryRule(ctx: ParserRuleContext): void {
const ruleName = this.parserRuleNames[ctx.ruleIndex];
if (ruleName) {
const count = this.parserRuleVisits.get(ruleName) || 0;
this.parserRuleVisits.set(ruleName, count + 1);
}
}
/**
* Called when exiting every parser rule
*/
exitEveryRule(_ctx: ParserRuleContext): void {
// Not needed for coverage tracking
}
/**
* Called when visiting a terminal node (token)
*/
visitTerminal(node: TerminalNode): void {
const tokenType = node.symbol.type;
// Token type -1 is EOF, skip it
if (tokenType < 0) return;
// Token types are 1-indexed in ANTLR, but the array is 0-indexed
// The first element (index 0) corresponds to token type 1
const ruleName = this.lexerRuleNames[tokenType - 1];
if (ruleName) {
const count = this.lexerRuleVisits.get(ruleName) || 0;
this.lexerRuleVisits.set(ruleName, count + 1);
}
}
/**
* Called when visiting an error node
*/
visitErrorNode(_node: ErrorNode): void {
// Track error nodes if needed in the future
}
/**
* Merge coverage from another listener (for aggregating across files)
*/
merge(other: GrammarCoverageListener): void {
for (const [rule, count] of other.parserRuleVisits) {
const current = this.parserRuleVisits.get(rule) || 0;
this.parserRuleVisits.set(rule, current + count);
}
for (const [rule, count] of other.lexerRuleVisits) {
const current = this.lexerRuleVisits.get(rule) || 0;
this.lexerRuleVisits.set(rule, current + count);
}
}
/**
* Reset all coverage counters
*/
reset(): void {
this.parserRuleVisits.clear();
this.lexerRuleVisits.clear();
}
/**
* Get the current parser rule visit counts
*/
getParserRuleVisits(): Map<string, number> {
return new Map(this.parserRuleVisits);
}
/**
* Get the current lexer rule visit counts
*/
getLexerRuleVisits(): Map<string, number> {
return new Map(this.lexerRuleVisits);
}
/**
* Generate a coverage report
*/
getReport(): IGrammarCoverageReport {
return GrammarCoverageReportBuilder.build({
parserRuleNames: this.parserRuleNames,
lexerRuleNames: this.lexerRuleNames,
parserRuleVisits: this.parserRuleVisits,
lexerRuleVisits: this.lexerRuleVisits,
});
}
}
export default GrammarCoverageListener;
|