Skip to content

feat: experimental tree-sitter dataflow engine (unused-assignment)#79

Draft
zmaril wants to merge 2 commits into
mainfrom
claude/dataflow-tree-sitter
Draft

feat: experimental tree-sitter dataflow engine (unused-assignment)#79
zmaril wants to merge 2 commits into
mainfrom
claude/dataflow-tree-sitter

Conversation

@zmaril

@zmaril zmaril commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Before / After

Before: dataflow-grade checks (bindings, def-use) exist only for JS/TS, through oxc's React rules. Every other language straitjacket scans gets the non-parsing tiers: regex over lines, codepoint scans, indentation counting, cpd-finder tokens.

After: an opt-in --dataflow flag (config key dataflow, off by default) runs a language-generic unused-assignment check (Warning: a value assigned to a local variable that is never read before reassignment or scope end) over Rust, TypeScript/TSX/JS, and Python via tree-sitter binding specs.

oxc is untouched. The React rules keep their parser — oxc is genuinely better for JS/TS, especially anything type-adjacent (see the portability audit in notes/dataflow.md). The generic engine exists for reach into the languages oxc will never cover. The flag defaults off, so existing scans, the dogfood self-scan, and every current user are unaffected.

How

Per-language binding spec + one generic engine. Each language contributes a single tree-sitter query file (src/dataflow/queries/*.scm, 68–79 lines each) whose captures come from a fixed vocabulary (@def/@assign/@ref/@scope/@loop/@branch/...), plus a small quirks entry in src/dataflow/spec.rs. One generic engine (src/dataflow/analysis.rs) parses, runs the query, builds the scope tree, resolves names lexically, and runs a function-local def-use pass. The load-bearing safety property: every ambiguity degrades to a read — an unclassified identifier stays a read, closure-touched variables are exempt, unparseable files skip — so a gap in a .scm file can mask a real finding but can never invent a false one.

Measured on the fleet

Ran --no-config --only unused-assignment --dataflow over powdermonkey, entl, disponent, and straitjacket (752 files), inspecting every finding by hand:

  • First working build: 4 findings, all 4 false positives (two classes: whole-construct branch regions letting if/else and try/catch arm writes kill each other; a Rust match-arm guard extracted as a binding). Both classes fixed and pinned with regression tests.
  • Committed build: 0 findings, 0 FPs on the fleet — the expected steady state for a precision-first rule over compiler/clippy/biome-clean code.
  • Recall probe: 4 known dead stores seeded into copies of real fleet files (TS, TSX, Rust, Python) — 4/4 detected, no extra findings. tests/dataflow.rs additionally pins 8 true-positive patterns and ~30 tricky negatives.
  • Cost: powdermonkey full scan 0.15s → 0.47s with --dataflow (415 files, release build).

Honest limits (by design, documented in src/dataflow/mod.rs)

  • No real CFG: branch nesting + a blanket loop back-edge exemption approximate it. Early return/?/break between two writes is unmodelled — false-negative-only by construction.
  • No aliasing (writes through *p/obj.field never count as tracked writes), no cross-function flow, no type info.
  • Python match patterns and del unmodelled (identifiers degrade to reads).
  • Rust format!-style {name} recovery scans macro string literals, so a brace-name in any other macro string also counts as a read (conservative).
  • Files that don't parse under the grammar are skipped silently — notably .js files containing JSX.

Full design rationale — the oxc portability audit, the design space considered (stack-graphs, Semgrep/Opengrep, CodeQL, Glean, SCIP, Joern), and the follow-up path (Go, use-before-def, CFG-lite if taint is ever required) — is in notes/dataflow.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WauUMFGGuk9J2rFa5ahHgr


Generated by Claude Code

claude added 2 commits July 9, 2026 23:38
An opt-in (--dataflow / config `dataflow`) language-generic dataflow
analysis proving the per-language-binding-spec + one-generic-engine
architecture. Each language (Rust, TypeScript/TSX/JS, Python) contributes
one tree-sitter query in a fixed capture vocabulary (@def/@assign/@ref/
@scope/@loop/@branch/...); a single engine builds the scope tree, resolves
names lexically, and runs a function-local def-use check.

One check ships: unused-assignment (Warning) — a value assigned to a local
variable that is never read before reassignment or scope end. Every
approximation errs toward not flagging: unclassified identifiers stay
reads, closure-touched variables are exempt, loops get a blanket back-edge
exemption, branch kills are arm-granular, and unparseable files skip.

Measured on powdermonkey/entl/disponent/straitjacket (752 files): the two
FP classes the first build produced (whole-construct branch regions letting
if/else arm writes kill each other; Rust match-arm guards extracted as
bindings) are fixed and regression-tested; the final build reports zero
findings on the fleet while catching all seeded dead stores. The oxc React
rules are untouched; the flag is off by default so the dogfood self-scan
is unaffected (and passes with the flag on too).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WauUMFGGuk9J2rFa5ahHgr
Records the design rationale for the experimental --dataflow engine: why
oxc stays for JS/TS (portability audit of all six oxc-backed checks), the
design space surveyed (stack-graphs, Semgrep's IL architecture, CodeQL/
Glean/SCIP/Joern) with the load-bearing facts, the binding-spec + generic-
engine architecture and its degrade-to-read safety property, the measured
fleet results (4 FPs found and fixed, 0 findings final, 4/4 seeded dead
stores caught, 0.15s to 0.47s scan cost), the honest limits, and the
follow-up path (Go, use-before-def, CFG-lite if taint is ever required).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WauUMFGGuk9J2rFa5ahHgr
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
straitjacket 9278411 Commit Preview URL

Branch Preview URL
Jul 09 2026, 11:44 PM

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants