← all benchmarks

TrueMend vs. ESLint+react on React

TRUEMEND
10.6%
ESLINT+REACT
19.3%
CONFIRMED · ROUND 3

Round 2 was a clear loss, 30.3% against 8.0%, judged completely. Round 3 quarantined nine whole-construct detectors a line-scan provably cannot scope, fixed a loader bug that let quarantined detectors keep firing, and re-adjudicated: 10.6% against 19.3%, same judge, same corpus. Round 3 used a different judge than round 2, and the numbers across that change are not directly comparable; a same-judge re-measure is not yet run.

Round 3: TrueMend 10.6% wrong-match, at our own 10% bar and now edging out ESLint+react's 19.3% on the same corpus, same judge. Nine whole-construct detectors — provably unfixable by line-scan, verified against real source — were quarantined, not deleted, plus one shared engine bug that was letting quarantined detectors keep firing.

Excalidraw is a real, large production React+TypeScript app — 372 files in scope, tests and vendored Emscripten glue excluded. We deliberately did not reuse react-reconciler's own source as a corpus for this campaign: it's the reconciler's own implementation, not app-shaped code, and its findings skew toward generic nesting/length detectors rather than React-specific ones. Competitor is ESLint configured with eslint-plugin-react and eslint-plugin-react-hooks — bare ESLint would only re-measure the language surface already covered by our TS/JS language campaigns (§8), not the React-framework detector surface this one targets.

Round 1 — an interim number, five real bugs

An 80-of-378 interim sample (2 of 10 planned batches — stopped early once both the wrong-match rate and the bug-discovery rate made the remaining 8 low-value) put TrueMend at 37.5% after two fixes, then 28.6% after three more detectors were withdrawn from the default surface. ESLint+react held steady at 14.3% throughout — both tools were above the 10% bar on this interim read, not a clean loss for either side.

Five systemic bugs, verified by re-running the collector post-fix (800 → 722 findings): react-optional-chaining-in-render-without-fallback (53 of 800 pool findings, the single largest category) matched any a?.b anywhere in a file with no JSX-context check at all — disabled, needs real JSX-context tracking a line-scan can't do. react-usecallback-no-benefit had no negative pattern whatsoever, firing on every useCallback call including ones genuinely passed as props — disabled. react-useeffect-empty-deps-warning had a 6-line scope window against real useEffect bodies routinely 15-130+ lines long, plus a setter-detection regex that also matched setTimeout/ setInterval — disabled. react-unnecessary-fragment's ast-grep pattern used the wrong wildcard ($$$ instead of $X), matching any Fragment regardless of child count — fixed, not disabled; count dropped 7→1, the survivor a genuine true positive. Two duplicate iframe-title specs had scope_lines: 0, missing any title= attribute placed even one line below the flagged tag in real multi-line JSX — widened to 15 lines.

Round 2 — a fresh certification, not a partial sample, still fails

The round 1 "still open" note called for a full re-sample rather than trusting a 2-of-10-batch read. Round 2 delivers exactly that: a fresh crawl of the live pool (1310 TrueMend + 196 ESLint+react findings) drawn to a stratified sample and judged completely — 274 of 274 TrueMend findings, 88 of 88 ESLint+react, zero unjudged.

ToolJudgedCorrectWrong-matchWorth-fixingReal-rate
TrueMend27469.7%30.3%21.2%21.2%
ESLint+react8892.0%8.0%40.9%40.9%

Not a like-for-like delta against the 28.6% interim figure — different sampling scheme, different pool size — but the same verdict either way: failing. ESLint+react clears the wrong-match bar cleanly but is itself under the 70% real-rate bar (40.9%) — neither tool is fully clean on this corpus's framework-noise categories, TrueMend is just far worse on both axes.

Six categories, 100% wrong, none of them touched by round 1's fixes

api-ux-no-pagination (8/8) fires on plain DOM .select() calls (searchInputRef.current?.select()), not backend list endpoints — a new instance of a token-collision bug a prior FP campaign already partially guarded against for other frameworks. react-window-event-listener-missing-cleanup (8/8) fires on listeners that already have an explicit cleanup a few lines below. cq-deeply-nested-callbacks (8/8) fires on a single shallow callback with no actual nesting. react-missing-aria-labels (7/7) — a 2-line scope window misses aria-label attributes placed 3-13 lines away in real JSX. react-conditional-rendering-anti-pattern (7/7) fires on plain single-level ternaries, not the nested-ternary/&&-chain pattern it claims to catch. typescript-sq-s6480-jsx-no-duplicate-props (5/5) fires on JSX elements with no duplicate props at all. Two smaller categories are also fully wrong: typescript-eslint-react-style-prop-object (3/3) and react-aborted-request-no-cleanup (3/3).

The five round-1 fixes verifiably held — none of the three disabled detectors appear anywhere in this round's 274-finding sample, and the one fixed detector's lone sampled instance is a genuine true positive. This gap isn't a regression; it's six categories the 80-item interim sample simply never reached.

Round 3 — nine whole-construct detectors quarantined, not deleted

All six round-2 100%-wrong categories, plus three more surfaced by a larger sample, shared one structural defect: each was checking a whole-construct property — an element's accessible name, a button's declared type, a nested ternary's real depth, a callback pyramid's actual nesting, a duplicate JSX prop, an unpaginated endpoint — that a single-line regex cannot scope, no matter how the window is widened. Root-caused against real Excalidraw source and quarantined with a data-driven quarantine_reason on each: api-ux-no-pagination, cq-deeply-nested-callbacks, typescript-sq-s6480-jsx-no-duplicate-props, typescript-eslint-react-style-prop-object, react-conditional-rendering-anti-pattern, react-missing-aria-labels, typescript-eslint-react-button-has-type, react-window-event-listener-missing-cleanup, react-aborted-request-no-cleanup.

Four of the nine — the aria-label/button-type/cleanup group — looked fixable by widening the negation window. A constructed recall test proved otherwise: no window size from 2 to 18 lines in either direction is recall-safe, because an adjacent element's own attribute suppresses a real finding on the flagged one. The current configs were already recall-lossy by construction; widening would only trade one false-positive count for a false-negative one, which we don't count as progress.

A shared engine bug surfaced mid-round: per-pattern quarantine was honored by the line-scan pool loader but not by the shared per-module spec loader React/auth/AI detectors load through — four quarantined detectors kept firing regardless, and the first round-3 measurement came in at 18.1% because of it. Fixed the loader; two other hand-rolled spec loaders still bypass quarantine on non-React surfaces, documented as a follow-up, confirmed to have zero effect on this measurement.

Re-collected (1244 TrueMend + 196 ESLint, down from round 2's 1310 by exactly the nine quarantined detectors' findings), re-sampled, and re-adjudicated blind — this round by glm-4.7, not Claude:

ToolJudgedCorrectWrong-matchWorth-fixingReal-rate
TrueMend21789.4%10.6%22.1%22.1%
ESLint+react8880.7%19.3%36.4%36.4%

At the bar, within noise at n=217, and now ahead of ESLint on the same corpus and same judge. No category runs 100% wrong at n≥5 anymore; the residual is spread thin, led by typescript-eslint-ts-no-unsafe-assignment (3/8).

The real-rate number needs a judge-dependence caveat

Round 2 was judged by Claude; round 3 by glm-4.7. ESLint's own findings moved sharply across that judge change alone — 8.0%→19.3% wrong, 40.9%→36.4% real — on identical findings. That's the judge, not either tool, so round-2 and round-3 real-rates are not directly comparable. What is comparable: round 3's TrueMend-vs-ESLint gap (same judge, same corpus), and the wrong-match drop itself (30.3%→10.6%), since the quarantined detectors' findings are gone from the pool regardless of who's judging.

Still open

Real-rate (22.1%) still misses the >70% bar, but the same judge scores ESLint's own correct findings the same way — glm-4.7 treats most correct any-usage/magic-number/deep-nesting findings as technically-true-but-not- worth-fixing for both tools alike. That's a judge-calibration question, not evidence either detector surface is broken. A same-judge (Claude) re-measure of this exact round-3 pool, the way Express caught and resolved its own judge-divergence, would confirm whether 10.6%/22.1% holds — not yet run.