@thinksharpe/react-compiler-unmemo 0.5.0
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/LICENSE +21 -0
- package/README.md +158 -0
- package/docs/architecture.md +157 -0
- package/docs/edge-cases.md +247 -0
- package/helpers/fix-type-annotations.mjs +153 -0
- package/helpers/remove-hooks.mjs +522 -0
- package/package.json +51 -0
- package/react-compiler-unmemo.mjs +128 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thinksharpe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# react-compiler-unmemo
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
|
|
6
|
+
A codemod that removes `useMemo` and `useCallback` from your React codebase so you can adopt [React Compiler](https://react.dev/learn/react-compiler) without the manual cleanup.
|
|
7
|
+
|
|
8
|
+
> **Note:** React Compiler works fine with existing manual memoization — it preserves and layers on top of it. Removing hooks is optional for cleanliness and readability, but **not required for performance**. Use this tool to declutter legacy code. Always preview with `--dry-run` and type-check afterward.
|
|
9
|
+
|
|
10
|
+
## Quick Start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx react-compiler-unmemo ./my-react-app
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This previews all changes without modifying any files (dry-run is the safe default). When you're ready to apply:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx react-compiler-unmemo ./my-react-app --write
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or clone and run locally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/thinksharpe/react-compiler-unmemo.git
|
|
26
|
+
cd react-compiler-unmemo
|
|
27
|
+
npm install
|
|
28
|
+
node react-compiler-unmemo.mjs ./path/to/your/project
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Origin
|
|
32
|
+
|
|
33
|
+
I tried using Claude Opus 4.5 to write a script that would remove `useMemo` and `useCallback` from my codebase, but it kept failing. Claude noticed that `sed` couldn't handle this kind of complex pattern matching — the nested parentheses, arrow functions, and dependency arrays made it impossible with simple text replacement. So it wrote this script instead.
|
|
34
|
+
|
|
35
|
+
I like to keep my code as readable as possible, and all those `useMemo` and `useCallback` hooks were adding extra complexity. I'm glad they're gone from my codebase now. Hope it helps someone else too.
|
|
36
|
+
|
|
37
|
+
## Before / After
|
|
38
|
+
|
|
39
|
+
```diff
|
|
40
|
+
- const value = useMemo(() => computeExpensiveValue(a, b), [a, b]);
|
|
41
|
+
+ const value = computeExpensiveValue(a, b);
|
|
42
|
+
|
|
43
|
+
- const handler = useCallback((e) => doSomething(e), [doSomething]);
|
|
44
|
+
+ const handler = (e) => doSomething(e);
|
|
45
|
+
|
|
46
|
+
- import { useMemo, useCallback, useState } from "react";
|
|
47
|
+
+ import { useState } from "react";
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Why
|
|
51
|
+
|
|
52
|
+
- **Cleaner code** — less boilerplate, easier to read and maintain
|
|
53
|
+
- **Aligns with modern React** — React Compiler handles memoization automatically
|
|
54
|
+
- **Smaller bundles** — unused hook imports are removed
|
|
55
|
+
- **Safe to re-run** — idempotent, won't touch already-processed files
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 1. Preview changes (safe default — no files modified)
|
|
61
|
+
node react-compiler-unmemo.mjs ./my-app
|
|
62
|
+
|
|
63
|
+
# 2. Apply changes
|
|
64
|
+
node react-compiler-unmemo.mjs ./my-app --write
|
|
65
|
+
|
|
66
|
+
# 3. Type-check your project
|
|
67
|
+
cd ./my-app && npx tsc --noEmit
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Options
|
|
71
|
+
|
|
72
|
+
| Flag | Description | Default |
|
|
73
|
+
|------|-------------|--------|
|
|
74
|
+
| *(no flag)* | Preview changes without writing files | **dry-run** |
|
|
75
|
+
| `--write` | Apply changes to files | off |
|
|
76
|
+
| `--verbose` | Log every transformation | off |
|
|
77
|
+
| `--files <glob>` | Limit to specific file patterns | `helpers/**/*.{tsx,ts}` |
|
|
78
|
+
| `--skip-fix` | Skip type annotation repair step | off |
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Only process specific directory
|
|
82
|
+
node react-compiler-unmemo.mjs ./my-app --files "hooks/**/*.ts" --write
|
|
83
|
+
|
|
84
|
+
# app directory
|
|
85
|
+
node react-compiler-unmemo.mjs ./my-app --files "app/**/*.{tsx,ts}"
|
|
86
|
+
|
|
87
|
+
# See every change in detail
|
|
88
|
+
node react-compiler-unmemo.mjs ./my-app --verbose
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## What It Handles
|
|
92
|
+
|
|
93
|
+
- `useMemo(() => expr, [deps])` → `expr`
|
|
94
|
+
- `useMemo(() => { return expr; }, [deps])` → `expr`
|
|
95
|
+
- `useMemo<Type>(...)` → strips the generic, restores the type annotation
|
|
96
|
+
- `useCallback((params) => body, [deps])` → `(params) => body`
|
|
97
|
+
- `React.useMemo` / `React.useCallback` variants
|
|
98
|
+
- Multi-line hooks spanning dozens of lines
|
|
99
|
+
- Nested generics like `useMemo<ColumnsType<MyType>>()`
|
|
100
|
+
- Import cleanup (removes unused `useMemo`/`useCallback` from imports)
|
|
101
|
+
|
|
102
|
+
## When NOT to Use
|
|
103
|
+
|
|
104
|
+
- **You haven't enabled React Compiler yet** — without the compiler, removing `useMemo`/`useCallback` may cause performance regressions
|
|
105
|
+
- **Intentional escape hatches** — some hooks are used deliberately to control referential identity for third-party libraries
|
|
106
|
+
- **Class component interop** — if memoized values are passed to class components that rely on shallow comparison
|
|
107
|
+
|
|
108
|
+
When in doubt, use `--dry-run` and review the output.
|
|
109
|
+
|
|
110
|
+
## Post-Migration
|
|
111
|
+
|
|
112
|
+
**Always run your type checker after the migration:**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx tsc --noEmit
|
|
116
|
+
# or your project's build command
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The tool handles the most common cases automatically, but some things need manual attention:
|
|
120
|
+
|
|
121
|
+
- **Hooks with `//` comments inside** — the parser may skip or partially transform these. Check the "remaining refs" count in the output.
|
|
122
|
+
- **Missing imports** — the type fixer adds `ColumnsType<T>` annotations but may not add the import statement.
|
|
123
|
+
- **Dangling `as Type` casts** — if a `useCallback` had an `as React.FC` cast, it may need cleanup.
|
|
124
|
+
- **Broken IIFEs** — complex `useMemo` bodies with comments may leave a trailing `, [deps])` instead of `})()`.
|
|
125
|
+
|
|
126
|
+
See [docs/edge-cases.md](./docs/edge-cases.md) for the full list with code examples.
|
|
127
|
+
|
|
128
|
+
## Project Structure
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
react-compiler-unmemo/
|
|
132
|
+
├── react-compiler-unmemo.mjs # Entry point
|
|
133
|
+
├── helpers/
|
|
134
|
+
│ ├── remove-hooks.mjs # Core hook removal
|
|
135
|
+
│ └── fix-type-annotations.mjs
|
|
136
|
+
├── docs/
|
|
137
|
+
│ ├── architecture.md # How it works under the hood
|
|
138
|
+
│ └── edge-cases.md # Known edge cases & fixes
|
|
139
|
+
├── package.json
|
|
140
|
+
└── README.md
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For technical details on the parser and pipeline, see [docs/architecture.md](./docs/architecture.md).
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
Issues, PRs, and edge-case examples welcome. Please open an issue first for large changes.
|
|
148
|
+
|
|
149
|
+
If you've run this on a real codebase and hit an edge case, sharing the pattern (even without proprietary code) helps improve the tool for everyone.
|
|
150
|
+
|
|
151
|
+
## Links
|
|
152
|
+
|
|
153
|
+
- [React Compiler — Official Guide](https://react.dev/learn/react-compiler)
|
|
154
|
+
- [React 19 — What's New](https://react.dev/blog/2024/12/05/react-19)
|
|
155
|
+
|
|
156
|
+
## License
|
|
157
|
+
|
|
158
|
+
MIT — see [LICENSE](./LICENSE)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Deep dive into how `react-compiler-unmemo` works under the hood.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The tool is a two-step pipeline:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
react-compiler-unmemo.mjs
|
|
11
|
+
├── Step 1: src/remove-hooks.mjs → strip useMemo/useCallback
|
|
12
|
+
└── Step 2: src/fix-type-annotations.mjs → restore lost type annotations
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Each script exports a `run(targetDir, options)` function and can also run standalone via CLI.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Step 1: Hook Removal (`src/remove-hooks.mjs`)
|
|
20
|
+
|
|
21
|
+
### Phase 1: Strip Generic Type Parameters
|
|
22
|
+
|
|
23
|
+
Before removing the hook call itself, we strip any TypeScript generic type parameters:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
useMemo<ColumnsType<TaxArea>>( → useMemo(
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This is done with a character scanner that counts nested `<>` brackets, since a simple regex like `<[^>]*>` fails on nested generics like `ColumnsType<TaxArea>`.
|
|
30
|
+
|
|
31
|
+
### Phase 2: Remove Hook Calls
|
|
32
|
+
|
|
33
|
+
For each occurrence of `useMemo(` or `useCallback(` (including `React.` prefixed variants):
|
|
34
|
+
|
|
35
|
+
1. **Find the matching closing paren** using a depth-counting parser that tracks `()`, `[]`, `{}` while respecting strings, template literals, and escape sequences.
|
|
36
|
+
|
|
37
|
+
2. **Strip the dependency array** by scanning for the last `, [` at depth 0 inside the call.
|
|
38
|
+
|
|
39
|
+
3. **Unwrap the arrow function** by finding `=>` at depth 0.
|
|
40
|
+
|
|
41
|
+
4. **Generate the replacement:**
|
|
42
|
+
|
|
43
|
+
| Pattern | Replacement |
|
|
44
|
+
|---------|-------------|
|
|
45
|
+
| `useMemo(() => expr, [deps])` | `expr` |
|
|
46
|
+
| `useMemo(() => { return expr; }, [deps])` | `expr` |
|
|
47
|
+
| `useMemo(() => { complex; body; }, [deps])` | `(() => { complex; body; })()` |
|
|
48
|
+
| `useCallback((a, b) => body, [deps])` | `(a, b) => body` |
|
|
49
|
+
|
|
50
|
+
5. **Loop** — since indices shift after each replacement, we restart the search after every successful replacement. A safety counter (200 max) prevents infinite loops.
|
|
51
|
+
|
|
52
|
+
### Phase 3: Clean Imports
|
|
53
|
+
|
|
54
|
+
After all hooks are removed, we clean up the import statements:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// Before
|
|
58
|
+
import { useMemo, useCallback, useState } from "react";
|
|
59
|
+
|
|
60
|
+
// After
|
|
61
|
+
import { useState } from "react";
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Handles both `import { ... }` and `import React, { ... }` styles. Empty imports are removed entirely.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Step 2: Fix Type Annotations (`src/fix-type-annotations.mjs`)
|
|
69
|
+
|
|
70
|
+
When `useMemo<ColumnsType<Foo>>(...)` is stripped, the generic type annotation is lost. This step scans for known patterns and restores the annotation on the variable:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Lost annotation
|
|
74
|
+
const columns = [...]
|
|
75
|
+
|
|
76
|
+
// Restored
|
|
77
|
+
const columns: ColumnsType<Foo> = [...]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Type Inference
|
|
81
|
+
|
|
82
|
+
The script infers the correct type parameter `T` from surrounding code context:
|
|
83
|
+
|
|
84
|
+
- **`ColumnsType<T>`** — looks for `SorterResult<T>`, `Table<T>`, or `ColumnType<T>` in the same file
|
|
85
|
+
- **`FormFieldProps[]`** — applied to any `formFields` array
|
|
86
|
+
|
|
87
|
+
Each pattern has an `alreadyTyped` regex to skip files that already have the annotation (idempotent).
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Parser Design
|
|
92
|
+
|
|
93
|
+
The core parser is character-by-character rather than regex-based. This is critical because:
|
|
94
|
+
|
|
95
|
+
1. **Nested brackets** — `useMemo<ColumnsType<TaxArea>>()` has nested `<>` that regex can't match
|
|
96
|
+
2. **Multi-line constructs** — hooks often span 20+ lines with complex nested objects
|
|
97
|
+
3. **Strings containing brackets** — `"some (text)"` inside a hook body would confuse regex
|
|
98
|
+
4. **Template literals** — `` `${expr}` `` with nested expressions
|
|
99
|
+
|
|
100
|
+
### String/Template Tracking
|
|
101
|
+
|
|
102
|
+
The parser tracks three states:
|
|
103
|
+
- **Normal** — counting brackets
|
|
104
|
+
- **In string** — `'...'` or `"..."`, respecting `\\` escapes
|
|
105
|
+
- **In template** — `` `...` ``, tracking `${...}` expression depth
|
|
106
|
+
|
|
107
|
+
### Comment Awareness
|
|
108
|
+
|
|
109
|
+
Before processing any match, `isInsideComment()` checks if the position is inside a `//` line comment or `/* */` block comment.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## File Structure
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
react-compiler-unmemo/
|
|
117
|
+
├── react-compiler-unmemo.mjs # Entry point / runner
|
|
118
|
+
├── src/
|
|
119
|
+
│ ├── remove-hooks.mjs # Core hook removal
|
|
120
|
+
│ └── fix-type-annotations.mjs # Type annotation fixer
|
|
121
|
+
├── docs/
|
|
122
|
+
│ ├── architecture.md # This file
|
|
123
|
+
│ └── edge-cases.md # Known edge cases
|
|
124
|
+
├── package.json
|
|
125
|
+
├── .gitignore
|
|
126
|
+
└── README.md
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Exported API
|
|
130
|
+
|
|
131
|
+
Both scripts export their `run()` function for programmatic use:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
import { run as removeHooks } from "./src/remove-hooks.mjs";
|
|
135
|
+
import { run as fixTypes } from "./src/fix-type-annotations.mjs"; // in react-compiler-unmemo.mjs
|
|
136
|
+
|
|
137
|
+
// Remove hooks from a directory
|
|
138
|
+
const result = removeHooks("/path/to/project", {
|
|
139
|
+
fileGlob: "src/**/*.{tsx,ts}",
|
|
140
|
+
dryRun: false,
|
|
141
|
+
verbose: true,
|
|
142
|
+
});
|
|
143
|
+
// result: { changedCount, totalTransformations, errors, remaining }
|
|
144
|
+
|
|
145
|
+
// Fix type annotations
|
|
146
|
+
const fixResult = fixTypes("/path/to/project", {
|
|
147
|
+
fileGlob: "src/**/*.{tsx,ts}",
|
|
148
|
+
dryRun: false,
|
|
149
|
+
});
|
|
150
|
+
// fixResult: { fixCount, errors }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Limitations
|
|
154
|
+
|
|
155
|
+
- **Does not use an AST parser** — the character-by-character approach is simpler and has no dependencies beyond `glob`, but it could theoretically be confused by extremely unusual code patterns
|
|
156
|
+
- **Type inference is heuristic** — the fix-type-annotations step only handles known patterns (`ColumnsType`, `FormFieldProps`). Other lost generics need manual annotation
|
|
157
|
+
- **IIFE fallback** — complex `useMemo` bodies with multiple statements become `(() => { ... })()` which works but isn't the prettiest
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Edge Cases & Post-Migration Fixes
|
|
2
|
+
|
|
3
|
+
After running `remove-hooks.mjs`, the following edge cases required manual fixes.
|
|
4
|
+
These are pre-existing type issues that were **hidden** by `useMemo`/`useCallback`'s
|
|
5
|
+
looser type inference and only surfaced once the wrappers were removed.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Lost Generic Type Annotations on `columns` Arrays
|
|
10
|
+
|
|
11
|
+
**Problem:** When `useMemo<ColumnsType<T>>(...)` is removed, the generic type
|
|
12
|
+
annotation `ColumnsType<T>` is stripped along with it. Without the annotation,
|
|
13
|
+
TypeScript infers a loose object literal type instead of `ColumnsType<T>`, causing
|
|
14
|
+
type mismatches with table `onChange` handlers.
|
|
15
|
+
|
|
16
|
+
**Error example:**
|
|
17
|
+
```
|
|
18
|
+
Type '(sorter: SorterResult<TaxArea>[] | SorterResult<TaxArea>) => void'
|
|
19
|
+
is not assignable to type '(sorter: SorterResult<{ id: any; ... }>) => void'.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Fix:** Add the type annotation directly to the variable:
|
|
23
|
+
```typescript
|
|
24
|
+
// Before (script output)
|
|
25
|
+
const columns = [
|
|
26
|
+
{ title: "Name", dataIndex: "name", ... },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// After (manual fix)
|
|
30
|
+
const columns: ColumnsType<TaxArea> = [
|
|
31
|
+
{ title: "Name", dataIndex: "name", ... },
|
|
32
|
+
];
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Lost Type Annotations on `formFields` Arrays
|
|
38
|
+
|
|
39
|
+
**Problem:** `FormFieldProps` has a union type for the `type` field
|
|
40
|
+
(`"text" | "select" | "switch" | ...`). When `useMemo` wraps the array,
|
|
41
|
+
TypeScript doesn't strictly check the object literal. Once removed,
|
|
42
|
+
`type: "text"` is inferred as `string`, which doesn't satisfy the union.
|
|
43
|
+
|
|
44
|
+
**Error example:**
|
|
45
|
+
```
|
|
46
|
+
Type 'string' is not assignable to type '"number" | "text" | "switch" | "select" | ...'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Fix:** Add `FormFieldProps[]` type annotation to the `formFields` variable:
|
|
50
|
+
```typescript
|
|
51
|
+
// Before
|
|
52
|
+
const formFields = [
|
|
53
|
+
{ label: "Name", name: "name", type: "text", span: 12 },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// After
|
|
57
|
+
const formFields: FormFieldProps[] = [
|
|
58
|
+
{ label: "Name", name: "name", type: "text", span: 12 },
|
|
59
|
+
];
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 3. Invalid Properties on Typed Objects (Pre-existing Bugs)
|
|
65
|
+
|
|
66
|
+
**Problem:** Some object literals contained properties that don't exist on
|
|
67
|
+
their target type. `useMemo`'s loose inference hid these errors.
|
|
68
|
+
|
|
69
|
+
### 3a. `scrollable` property on column definitions
|
|
70
|
+
```typescript
|
|
71
|
+
// Invalid — 'scrollable' doesn't exist on ColumnType<Manifest>
|
|
72
|
+
{ title: "PO", scrollable: true, ... }
|
|
73
|
+
|
|
74
|
+
// Fix: remove the invalid property
|
|
75
|
+
{ title: "PO", ... }
|
|
76
|
+
```
|
|
77
|
+
### 3b. `sorterIndex` typo (should be `sortIndex`)
|
|
78
|
+
```typescript
|
|
79
|
+
// Invalid
|
|
80
|
+
{ sorterIndex: "name" }
|
|
81
|
+
|
|
82
|
+
// Fix
|
|
83
|
+
{ sortIndex: "name" }
|
|
84
|
+
```
|
|
85
|
+
### 3c. `required` and `addonAfter` on `FormFieldProps`
|
|
86
|
+
```typescript
|
|
87
|
+
// Invalid — these properties don't exist on FormFieldProps
|
|
88
|
+
{ required: false, addonAfter: <UserOutlined /> }
|
|
89
|
+
|
|
90
|
+
// Fix: remove the invalid properties
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3d. Method name typos exposed by stricter inference
|
|
94
|
+
```typescript
|
|
95
|
+
// Invalid
|
|
96
|
+
form.setFieldsValues({ finalAim: newFinalAimType });
|
|
97
|
+
|
|
98
|
+
// Fix
|
|
99
|
+
form.setFieldsValue({ finalAim: newFinalAimType });
|
|
100
|
+
```
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 4. Implicit `any` Parameters
|
|
104
|
+
|
|
105
|
+
**Problem:** When `useCallback` wraps a function, TypeScript can sometimes
|
|
106
|
+
infer parameter types from context. Once the wrapper is removed, parameters
|
|
107
|
+
may lose their inferred types and become implicit `any`.
|
|
108
|
+
|
|
109
|
+
**Error example:**
|
|
110
|
+
```
|
|
111
|
+
Parameter '_' implicitly has an 'any' type.
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Fix:** Add explicit type annotations to the parameters:
|
|
115
|
+
```typescript
|
|
116
|
+
// Before
|
|
117
|
+
render: (_, record) => record.customer?.comment || "",
|
|
118
|
+
|
|
119
|
+
// After
|
|
120
|
+
render: (_: unknown, record: CustomerRouteFrequency) => record.customer?.comment || "",
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 5. `useMemo` Returning a Value vs. Function
|
|
126
|
+
|
|
127
|
+
**Problem:** The script converts `useMemo(() => expr, [deps])` to just `expr`.
|
|
128
|
+
But if `expr` is an object or complex expression, the result may need parens
|
|
129
|
+
to avoid parsing ambiguity.
|
|
130
|
+
|
|
131
|
+
**Example:**
|
|
132
|
+
```typescript
|
|
133
|
+
// useMemo(() => ({ key: value }), [deps])
|
|
134
|
+
// Correctly becomes:
|
|
135
|
+
const obj = ({ key: value });
|
|
136
|
+
|
|
137
|
+
// useMemo(() => { return entity || DEFAULT; }, [entity])
|
|
138
|
+
// Correctly becomes (IIFE for complex bodies):
|
|
139
|
+
const item = (() => { ... })();
|
|
140
|
+
// Or for simple return:
|
|
141
|
+
const item = (entity || DEFAULT);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The script handles both cases automatically.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## 6. Hooks with Embedded Comments (Script Limitation)
|
|
149
|
+
|
|
150
|
+
**Problem:** The script's comment detection can cause it to skip hooks that contain
|
|
151
|
+
`//` comments inside their body. The script sees the `//` and thinks the entire
|
|
152
|
+
hook call is inside a comment.
|
|
153
|
+
|
|
154
|
+
**Symptom:** The hook is left untouched after running the script, or is partially
|
|
155
|
+
transformed (e.g. the wrapper is removed but the dependency array is left behind).
|
|
156
|
+
|
|
157
|
+
**Example — skipped entirely:**
|
|
158
|
+
```typescript
|
|
159
|
+
// Script cannot process this because of the // comments inside
|
|
160
|
+
const findOption = useCallback(
|
|
161
|
+
(value: number) => {
|
|
162
|
+
// No dependencies since we're using a ref
|
|
163
|
+
return optionsRef.current.find((opt) => opt.value === value);
|
|
164
|
+
},
|
|
165
|
+
[],
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Fix: manually remove the wrapper
|
|
169
|
+
const findOption = (value: number) => {
|
|
170
|
+
// No dependencies since we're using a ref
|
|
171
|
+
return optionsRef.current.find((opt) => opt.value === value);
|
|
172
|
+
};
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Example — broken IIFE with leftover dependency array:**
|
|
176
|
+
```typescript
|
|
177
|
+
// Script partially transforms, leaving ", [deps])" behind
|
|
178
|
+
const orderBy = (() => {
|
|
179
|
+
// Default ordering
|
|
180
|
+
return { name: "AscNullsLast" };
|
|
181
|
+
}, [order]); // <-- broken: should be "})();"
|
|
182
|
+
|
|
183
|
+
// Fix:
|
|
184
|
+
const orderBy = (() => {
|
|
185
|
+
// Default ordering
|
|
186
|
+
return { name: "AscNullsLast" };
|
|
187
|
+
})();
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 7. `as Type` Casts on Function Expressions
|
|
193
|
+
|
|
194
|
+
**Problem:** When `useCallback` wraps a function that has an `as Type` cast at
|
|
195
|
+
the end, removing the wrapper leaves a bare function expression with a dangling
|
|
196
|
+
cast that may not parse correctly.
|
|
197
|
+
|
|
198
|
+
**Example:**
|
|
199
|
+
```typescript
|
|
200
|
+
// Before
|
|
201
|
+
const Toggle = useCallback(() => {
|
|
202
|
+
return React.createElement(Button, { onClick: handleToggle });
|
|
203
|
+
}, [handleToggle]) as React.FC;
|
|
204
|
+
|
|
205
|
+
// Script output (broken)
|
|
206
|
+
const Toggle: React.FC = () => {
|
|
207
|
+
return React.createElement(Button, { onClick: handleToggle });
|
|
208
|
+
} as React.FC;
|
|
209
|
+
|
|
210
|
+
// Fix: remove the dangling cast (the variable already has the type)
|
|
211
|
+
const Toggle: React.FC = () => {
|
|
212
|
+
return React.createElement(Button, { onClick: handleToggle });
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 8. Missing `ColumnsType` Import
|
|
219
|
+
|
|
220
|
+
**Problem:** The fix-type-annotations step adds `ColumnsType<T>` annotations to
|
|
221
|
+
`columns` arrays, but does not add the corresponding import. If the file didn't
|
|
222
|
+
already import `ColumnsType`, you'll get a "Cannot find name" error.
|
|
223
|
+
|
|
224
|
+
**Error example:**
|
|
225
|
+
```
|
|
226
|
+
Cannot find name 'ColumnsType'.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Fix:** Add the import:
|
|
230
|
+
```typescript
|
|
231
|
+
import type { ColumnsType } from "antd/es/table";
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Summary Checklist
|
|
237
|
+
|
|
238
|
+
After running the script, run `npx tsc --noEmit` (or your build command) and check for:
|
|
239
|
+
|
|
240
|
+
1. **`ColumnsType<T>` annotations** — any file that had `useMemo<ColumnsType<T>>`
|
|
241
|
+
2. **Missing `ColumnsType` import** — add `import type { ColumnsType } from "antd/es/table"`
|
|
242
|
+
3. **`FormFieldProps[]` annotations** — any file with `formFields` arrays
|
|
243
|
+
4. **Implicit `any` errors** — render functions in column definitions
|
|
244
|
+
5. **Invalid properties** — properties that were silently ignored before
|
|
245
|
+
6. **Hooks with embedded comments** — may be skipped or partially transformed
|
|
246
|
+
7. **Dangling `as Type` casts** — remove if the variable already has the type
|
|
247
|
+
8. **Broken IIFEs** — leftover `, [deps])` instead of `)()`
|