roto-rooter 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,133 +1,129 @@
1
1
  # roto-rooter
2
2
 
3
- Static analysis and functional verifier tool for React Router applications.
3
+ 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.
4
4
 
5
- ## Installation
6
-
7
- ```bash
5
+ ```
8
6
  npm install -g roto-rooter
9
7
  ```
10
8
 
11
- ## Usage
9
+ ## Running Checks
12
10
 
13
- ```bash
14
- # Check all files in current directory
15
- rr
11
+ ```
12
+ rr [OPTIONS] [FILES...]
13
+ ```
16
14
 
17
- # Check specific file(s)
18
- rr app/routes/employees.tsx
15
+ Point `rr` at your project and it scans your route files for issues. With no arguments it runs the **default checks** (links, loader, params, interactivity) against all files in the current directory.
19
16
 
20
- # Run specific checks only
21
- rr --check links,forms
17
+ | Option | Description |
18
+ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `-c, --check <checks>` | 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` |
20
+ | `-f, --format <format>` | Output format: `text` (default) or `json` |
21
+ | `-r, --root <path>` | Project root containing the `app/` folder (default: cwd) |
22
+ | `--fix` | Auto-fix issues where possible |
23
+ | `--dry-run` | Preview fixes without writing files |
24
+ | `--drizzle-schema <path>` | Path to Drizzle schema file (auto-discovered by default) |
22
25
 
23
- # Run all checks (including optional ones)
24
- rr --check all
26
+ **Checks at a glance:**
25
27
 
26
- # Run default checks plus specific optional checks
27
- rr --check defaults,forms
28
+ - **links** (default) -- validates `<Link>`, `redirect()`, `navigate()`, and `href` props on any component exist as routes
29
+ - **loader** (default) -- ensures `useLoaderData()` is backed by a loader; catches `clientLoader` importing server-only modules
30
+ - **params** (default) -- ensures `useParams()` only accesses params defined in the route path
31
+ - **interactivity** (default) -- catches "Save" buttons that don't save, "Delete" buttons that don't delete, and empty click handlers
32
+ - **forms** (opt-in) -- validates `<Form>` targets have actions and that field names match `formData.get()` calls
33
+ - **hydration** (opt-in) -- detects SSR/client mismatches from `new Date()`, `Math.random()`, `window` access in render
34
+ - **drizzle** (opt-in) -- validates Drizzle ORM operations against your schema (missing columns, type mismatches, etc.)
28
35
 
29
- # Output as JSON
30
- rr --format json
36
+ **Example output:**
31
37
 
32
- # Set project root (the directory containing the app/ folder)
33
- rr --root ./my-app
38
+ ```
39
+ $ rr --root my-app
40
+
41
+ rr found 5 issues:
34
42
 
35
- # Automatically fix issues where possible
36
- rr --fix
43
+ [error] dashboard.tsx:12:7
44
+ href="/employeees"
45
+ x No matching route
46
+ -> Did you mean: /employees?
37
47
 
38
- # Preview fixes without applying
39
- rr --dry-run
48
+ [error] tasks.tsx:6:16
49
+ useLoaderData()
50
+ x useLoaderData() called but route has no loader
51
+ -> Add a loader function or remove the hook
40
52
 
41
- # Fix specific file(s)
42
- rr --fix app/routes/dashboard.tsx
53
+ [error] employees.$id.edit.tsx:7:32
54
+ useParams().invalidParam
55
+ x useParams() accesses "invalidParam" but route has no :invalidParam parameter
56
+ -> Available params: :id
43
57
 
44
- # Enable Drizzle ORM persistence checking (auto-discovers schema)
45
- rr --check drizzle
58
+ [error] disconnected-dialog.tsx:27:11
59
+ <Button onClick={...}>Save Changes</Button>
60
+ x "Save Changes" button in Dialog only closes dialog without saving data
61
+ -> Wrap inputs in a <Form> component or use useFetcher.submit() to persist data
46
62
 
47
- # Drizzle checking with explicit schema path
48
- rr --check drizzle --drizzle-schema src/db/schema.ts
63
+ [warning] disconnected-dialog.tsx:78:11
64
+ <Button onClick={...}>Add Item</Button>
65
+ x "Add Item" button has an empty or stub onClick handler
66
+ -> Implement the handler or remove the button if not needed
49
67
 
50
- # Extract SQL queries from Drizzle ORM code
51
- rr sql --drizzle
68
+ Summary: 4 errors, 1 warning
69
+ Run with --help for options.
70
+ ```
52
71
 
53
- # Extract queries from a specific file
54
- rr sql --drizzle app/routes/users.tsx
72
+ ## Extracting SQL
55
73
 
56
- # SQL output as JSON
57
- rr sql --drizzle --format json
58
74
  ```
75
+ rr sql --drizzle [OPTIONS] [FILES...]
76
+ ```
77
+
78
+ 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.
79
+
80
+ | Option | Description |
81
+ | ------------------------- | -------------------------------------------------------- |
82
+ | `--drizzle` | Required. Specifies the ORM to analyze. |
83
+ | `-f, --format <format>` | Output format: `text` (default) or `json` |
84
+ | `-r, --root <path>` | Project root directory (default: cwd) |
85
+ | `--drizzle-schema <path>` | Path to Drizzle schema file (auto-discovered by default) |
86
+
87
+ **Example output:**
59
88
 
60
- ## Checks
61
-
62
- **Default checks** (run automatically):
63
-
64
- - **links**: Validates `<Link>`, `redirect()`, and `navigate()` targets exist as defined routes. Suggests closest matching route when a typo is detected. Auto-fixable when a close match exists.
65
- - **loader**: Validates `useLoaderData()` is only used in routes that export a loader function. Detects `clientLoader`/`clientAction` that import server-only modules (database drivers, `fs`, etc.) which will fail in the browser. Auto-fixable by renaming to `loader`/`action`.
66
- - **params**: Validates `useParams()` accesses only params defined in the route path (e.g., `:id` in `/users/:id`).
67
- - **interactivity**: Detects disconnected interactive elements:
68
- - Dialog/modal forms where "Save" button only closes the dialog without persisting data
69
- - "Delete" confirmation buttons that only close without performing the action
70
- - Buttons with empty or stub onClick handlers (console.log only)
71
- - Validates dialogs use React Router `<Form>` or `useFetcher.submit()` for data operations
72
-
73
- **Optional checks** (opt-in via `--check`):
74
-
75
- - **forms**: Validates `<Form>` components submit to routes with action exports, and that form fields match what the action reads via `formData.get()`. Supports intent-based dispatch patterns. Auto-fixable when targeting a mistyped route.
76
- - **hydration**: Detects SSR hydration mismatch risks:
77
- - Date/time operations without consistent timezone handling
78
- - Locale-dependent formatting (e.g., `toLocaleString()`) without explicit `timeZone` option
79
- - Random value generation during render (`Math.random()`, `uuid()`, `nanoid()`)
80
- - Browser-only API access outside `useEffect` (`window`, `document`, `localStorage`)
81
-
82
- Some hydration issues are auto-fixable (e.g., adding `{ timeZone: "UTC" }` to locale methods, replacing `uuid()` with `useId()`).
83
-
84
- - **drizzle** (persistence): Validates database operations against Drizzle ORM schema. Auto-discovers schema from common locations (`db/schema.ts`, `src/db/schema.ts`, etc.) or use `--drizzle-schema` for custom paths.
85
- - Unknown table or column references in `db.insert()`, `db.update()`, `db.delete()`
86
- - Missing required columns on `db.insert()` calls
87
- - Null literal assigned to `notNull` column (insert or update)
88
- - Invalid enum literal values (checked against schema-defined allowed values)
89
- - Type mismatches: string from `formData.get()` to integer, boolean, timestamp, or json column
90
- - Writing to auto-generated columns (e.g., serial, auto-increment) on insert
91
- - `DELETE` or `UPDATE` without `.where()` clause (affects all rows)
92
- - Enum columns receiving unvalidated external input
93
-
94
- ## SQL Query Extraction
95
-
96
- The `rr sql` command extracts database queries from ORM code and generates equivalent SQL statements.
97
-
98
- ```bash
99
- rr sql --drizzle # extract all SQL queries
100
- rr sql --drizzle app/routes/users.tsx # extract from specific file
101
- rr sql --drizzle --format json # JSON output
102
- rr sql --drizzle --drizzle-schema db/schema.ts # explicit schema path
103
89
  ```
90
+ $ rr sql --drizzle --root my-app
91
+
92
+ Found 6 SQL queries:
104
93
 
105
- Supports SELECT, INSERT, UPDATE, and DELETE patterns with parameterized queries and column type inference from the schema.
94
+ File: app/routes/users.tsx:13:26
95
+ SELECT * FROM users
106
96
 
107
- ## Programmatic API
97
+ File: app/routes/users.tsx:16:9
98
+ SELECT id, name, email FROM users
108
99
 
109
- ```typescript
110
- import { analyze, applyFixes } from 'roto-rooter';
100
+ File: app/routes/users.tsx:24:29
101
+ SELECT * FROM users WHERE status = 'active'
111
102
 
112
- // Run analysis
113
- const result = analyze({
114
- root: './my-app',
115
- files: [], // empty = all files
116
- checks: [], // empty = all checks
117
- format: 'text',
118
- });
103
+ File: app/routes/users.tsx:36:9
104
+ INSERT INTO users (name, email, status) VALUES ($1, $2, $3)
105
+ Parameters:
106
+ $1: name (text)
107
+ $2: email (text)
108
+ $3: status (enum)
119
109
 
120
- console.log(result.issues);
110
+ File: app/routes/orders.tsx:16:9
111
+ INSERT INTO orders (status, user_id, total) VALUES ($1, $2, $3)
112
+ Parameters:
113
+ $1: status (enum)
114
+ $2: userId (integer)
115
+ $3: total (integer)
121
116
 
122
- // Apply auto-fixes
123
- const fixResult = applyFixes(result.issues);
124
- console.log(`Fixed ${fixResult.fixesApplied} issues`);
117
+ File: app/routes/users.tsx:42:9
118
+ DELETE FROM users WHERE id = $1
119
+ Parameters:
120
+ $1: Number(params.id) (serial)
125
121
  ```
126
122
 
127
123
  ## Development
128
124
 
129
- ```bash
130
- npm install # Install dependencies
131
- npm test # Run tests
132
- npm run build # Build for distribution
125
+ ```
126
+ npm install # install dependencies
127
+ npm test # run tests
128
+ npm run build # build for distribution
133
129
  ```