https://github.com/glideapps/roto-rooter
Static analysis and functional verifier tool for React Router applications
https://github.com/glideapps/roto-rooter
Last synced: 4 months ago
JSON representation
Static analysis and functional verifier tool for React Router applications
- Host: GitHub
- URL: https://github.com/glideapps/roto-rooter
- Owner: glideapps
- Created: 2026-01-16T20:12:57.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-02-12T20:16:16.000Z (4 months ago)
- Last Synced: 2026-02-13T04:31:40.713Z (4 months ago)
- Language: TypeScript
- Homepage:
- Size: 304 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# roto-rooter
A static analysis tool for [React Router](https://reactrouter.com/) apps. It catches common bugs -- broken links, missing loaders, hydration mismatches, disconnected UI elements, and incorrect database operations -- by reading your route definitions and cross-referencing them against your components.
```
npm install -g roto-rooter
```
## Running Checks
```
rr [OPTIONS] [FILES...]
```
Point `rr` at your project and it scans your route files for issues. With no arguments it runs the **default checks** (links, forms, loader, params, interactivity, hydration) against all files in the current directory.
| Option | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-c, --check ` | Comma-separated checks to run. Use `defaults` for the default set, `all` for everything, or pick individual checks: `links`, `loader`, `params`, `interactivity`, `forms`, `hydration`, `drizzle` |
| `-f, --format ` | Output format: `text` (default) or `json` |
| `-r, --root ` | Project root containing the `app/` folder (default: cwd) |
| `--fix` | Auto-fix issues where possible |
| `--dry-run` | Preview fixes without writing files |
| `--drizzle-schema ` | Path to Drizzle schema file (auto-discovered by default) |
**Checks at a glance:**
- **links** (default) -- validates ``, `redirect()`, `navigate()`, and `href` props on any component exist as routes
- **loader** (default) -- ensures `useLoaderData()` is backed by a loader; catches `clientLoader` importing server-only modules
- **params** (default) -- ensures `useParams()` only accesses params defined in the route path
- **interactivity** (default) -- catches "Save" buttons that don't save, "Delete" buttons that don't delete, and empty click handlers
- **hydration** (default) -- detects SSR/client mismatches from `new Date()`, `Math.random()`, locale-dependent formatting, `window` access in render
- **forms** (default) -- validates `` targets have actions and that field names match `formData.get()` calls
- **drizzle** (opt-in) -- validates Drizzle ORM operations against your schema (missing columns, type mismatches, etc.)
**Example output:**
```
$ rr --root my-app
rr found 5 issues:
[error] dashboard.tsx:12:7
href="/employeees"
x No matching route
-> Did you mean: /employees?
[error] tasks.tsx:6:16
useLoaderData()
x useLoaderData() called but route has no loader
-> Add a loader function or remove the hook
[error] employees.$id.edit.tsx:7:32
useParams().invalidParam
x useParams() accesses "invalidParam" but route has no :invalidParam parameter
-> Available params: :id
[error] disconnected-dialog.tsx:27:11
Save Changes
x "Save Changes" button in Dialog only closes dialog without saving data
-> Wrap inputs in a component or use useFetcher.submit() to persist data
[warning] disconnected-dialog.tsx:78:11
Add Item
x "Add Item" button has an empty or stub onClick handler
-> Implement the handler or remove the button if not needed
Summary: 4 errors, 1 warning
Run with --help for options.
```
## Extracting SQL
```
rr sql --drizzle [OPTIONS] [FILES...]
```
Reads your Drizzle ORM code and prints the equivalent SQL for every query it finds. Useful for reviewing what your app actually sends to the database.
| Option | Description |
| ------------------------- | -------------------------------------------------------- |
| `--drizzle` | Required. Specifies the ORM to analyze. |
| `-f, --format ` | Output format: `text` (default) or `json` |
| `-r, --root ` | Project root directory (default: cwd) |
| `--drizzle-schema ` | Path to Drizzle schema file (auto-discovered by default) |
**Example output:**
```
$ rr sql --drizzle --root my-app
Found 6 SQL queries:
File: app/routes/users.tsx:13:26
SELECT * FROM users
File: app/routes/users.tsx:16:9
SELECT id, name, email FROM users
File: app/routes/users.tsx:24:29
SELECT * FROM users WHERE status = 'active'
File: app/routes/users.tsx:36:9
INSERT INTO users (name, email, status) VALUES ($1, $2, $3)
Parameters:
$1: name (text)
$2: email (text)
$3: status (enum)
File: app/routes/orders.tsx:16:9
INSERT INTO orders (status, user_id, total) VALUES ($1, $2, $3)
Parameters:
$1: status (enum)
$2: userId (integer)
$3: total (integer)
File: app/routes/users.tsx:42:9
DELETE FROM users WHERE id = $1
Parameters:
$1: Number(params.id) (serial)
```
## Development
```
npm install # install dependencies
npm test # run tests
npm run build # build for distribution
```