Skip to content

JSON --stats parity: multiple summary events for named files, matched_lines under -o, and a stray summary with -c #101

Closed

Description

@shengyfu

While fixing a --json stats bug in #100, I built a differential harness against real rg 15.2.0 and found three further divergences in the JSON summary event. These are pre-existing and separate from #100, so I scoped them out of that PR rather than expand it.

Important: in every case below the search results themselves are correct. Only the stats accounting / event structure diverges. This matters for tooling that consumes --json, not for interactive use.

Current state after #100: all single-file and directory-search cases match ripgrep (5/5 in the harness). The three below are what's left.

1. One summary event per file when multiple files are named explicitly

ripgrep emits exactly one aggregate summary for the whole invocation. tgrep emits one per named path.

$ rg --json -v alpha a.txt b.txt   # one summary
{"matches": 0, "matched_lines": 4, "searches": 2, "searches_with_match": 2}

$ tgrep --json -v alpha a.txt b.txt   # two summaries, one per file
{"matches": 0, "matched_lines": 2, "searches": 1, "searches_with_match": 1}
{"matches": 0, "matched_lines": 2, "searches": 1, "searches_with_match": 1}

A consumer reading "the" summary gets only the first file's numbers. The searches counter divergence (1 vs 2) follows directly from this.

Scope note: directory searches already emit exactly one summary and match rg. This is specific to multiple explicitly named paths.

Why it's not a one-liner: run_search in tgrep-cli/src/main.rs loops over targets calling search::run per target, and each call constructs and flushes its own writer — flush() in output.rs is what emits the summary. Fixing it means hoisting writer/stats ownership above the loop so stats accumulate across targets and the summary is emitted once at the end. That touches all 8 writer.flush() sites in search.rs.

2. matched_lines over-counts under -o

$ rg --json -o alpha a.txt
{"matches": 3, "matched_lines": 2, ...}

$ tgrep --json -o alpha a.txt
{"matches": 3, "matched_lines": 3, ...}

Fixture a.txt is alpha / beta / gamma / alpha again alpha — 3 matches across 2 distinct lines.

Two related things: rg counts distinct lines, while tgrep does matched_lines += 1 per emitted event, which over-counts when -o fans a line out into several events. rg also structures the stream differently — it emits one match event per line with multiple submatches (begin, match, match, end, summary = 2 match events for 3 matches), whereas tgrep emits one event per match.

3. --json -c emits a summary where ripgrep emits no JSON at all

$ rg --json -c alpha a.txt
2

$ tgrep --json -c alpha a.txt
2
{"type":"summary", ...}

With -c, ripgrep's count output wins outright and --json is ignored. tgrep prints the plain count and a JSON summary, which is internally inconsistent — a consumer gets a line of JSON it can't associate with any begin/end pair.

Reproducing

The harness lives outside the repo (session scratch), but it's ~40 lines: run both binaries over a fixture set, parse the first summary event from each, and diff the stats object. Fixtures are a.txt = alpha / beta / gamma / alpha again alpha and b.txt = beta / delta.

Suggested order: #1 is the highest value (it's the one that actively misleads tooling), then #3 (smallest), then #2.

["bug"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions