roto-rooter 0.7.1 → 0.8.1

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,130 +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
- # Output as JSON
27
- rr --format json
28
+ - **links** (default) -- validates `<Link>`, `redirect()`, `navigate()` targets 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.)
35
+
36
+ **Example output:**
37
+
38
+ ```
39
+ $ rr --root my-app
28
40
 
29
- # Set project root (the directory containing the app/ folder)
30
- rr --root ./my-app
41
+ rr found 5 issues:
31
42
 
32
- # Automatically fix issues where possible
33
- rr --fix
43
+ [error] dashboard.tsx:12:7
44
+ href="/employeees"
45
+ x No matching route
46
+ -> Did you mean: /employees?
34
47
 
35
- # Preview fixes without applying
36
- 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
37
52
 
38
- # Fix specific file(s)
39
- 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
40
57
 
41
- # Enable Drizzle ORM persistence checking (auto-discovers schema)
42
- 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
43
62
 
44
- # Drizzle checking with explicit schema path
45
- 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
46
67
 
47
- # Extract SQL queries from Drizzle ORM code
48
- rr sql --drizzle
68
+ Summary: 4 errors, 1 warning
69
+ Run with --help for options.
70
+ ```
49
71
 
50
- # Extract queries from a specific file
51
- rr sql --drizzle app/routes/users.tsx
72
+ ## Extracting SQL
52
73
 
53
- # SQL output as JSON
54
- rr sql --drizzle --format json
55
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:**
56
88
 
57
- ## Checks
58
-
59
- **Default checks** (run automatically):
60
-
61
- - **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.
62
- - **loader**: Validates `useLoaderData()` is only used in routes that export a loader function.
63
- - **params**: Validates `useParams()` accesses only params defined in the route path (e.g., `:id` in `/users/:id`).
64
- - **interactivity**: Detects disconnected interactive elements:
65
- - Dialog/modal forms where "Save" button only closes the dialog without persisting data
66
- - "Delete" confirmation buttons that only close without performing the action
67
- - Buttons with empty or stub onClick handlers (console.log only)
68
- - Validates dialogs use React Router `<Form>` or `useFetcher.submit()` for data operations
69
-
70
- **Optional checks** (opt-in via `--check`):
71
-
72
- - **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.
73
- - **hydration**: Detects SSR hydration mismatch risks:
74
- - Date/time operations without consistent timezone handling
75
- - Locale-dependent formatting (e.g., `toLocaleString()`) without explicit `timeZone` option
76
- - Random value generation during render (`Math.random()`, `uuid()`, `nanoid()`)
77
- - Browser-only API access outside `useEffect` (`window`, `document`, `localStorage`)
78
-
79
- Some hydration issues are auto-fixable (e.g., adding `{ timeZone: "UTC" }` to locale methods, replacing `uuid()` with `useId()`).
80
-
81
- - **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.
82
- - Unknown table or column references in `db.insert()`, `db.update()`, `db.delete()`
83
- - Missing required columns on `db.insert()` calls
84
- - Null literal assigned to `notNull` column (insert or update)
85
- - Invalid enum literal values (checked against schema-defined allowed values)
86
- - Type mismatches: string from `formData.get()` to integer, boolean, timestamp, or json column
87
- - Writing to auto-generated columns (e.g., serial, auto-increment) on insert
88
- - `DELETE` or `UPDATE` without `.where()` clause (affects all rows)
89
- - Enum columns receiving unvalidated external input
90
-
91
- ## SQL Query Extraction
92
-
93
- The `rr sql` command extracts database queries from ORM code and generates equivalent SQL statements.
94
-
95
- ```bash
96
- rr sql --drizzle # extract all SQL queries
97
- rr sql --drizzle app/routes/users.tsx # extract from specific file
98
- rr sql --drizzle --format json # JSON output
99
- rr sql --drizzle --drizzle-schema db/schema.ts # explicit schema path
100
89
  ```
90
+ $ rr sql --drizzle --root my-app
91
+
92
+ Found 6 SQL queries:
101
93
 
102
- 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
103
96
 
104
- ## Programmatic API
97
+ File: app/routes/users.tsx:16:9
98
+ SELECT id, name, email FROM users
105
99
 
106
- ```typescript
107
- import { analyze, applyFixes } from 'roto-rooter';
100
+ File: app/routes/users.tsx:24:29
101
+ SELECT * FROM users WHERE status = 'active'
108
102
 
109
- // Run analysis
110
- const result = analyze({
111
- root: './my-app',
112
- files: [], // empty = all files
113
- checks: [], // empty = all checks
114
- format: 'text',
115
- });
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)
116
109
 
117
- 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)
118
116
 
119
- // Apply auto-fixes
120
- const fixResult = applyFixes(result.issues);
121
- 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)
122
121
  ```
123
122
 
124
123
  ## Development
125
124
 
126
- ```bash
127
- npm install # Install dependencies
128
- npm test # Run tests
129
- npm run build # Build for distribution
125
+ ```
126
+ npm install # install dependencies
127
+ npm test # run tests
128
+ npm run build # build for distribution
130
129
  ```