@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 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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
4
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](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 `)()`