|
| 1 | +# Codemod for TextField multiline to Textarea Migration |
| 2 | + |
| 3 | +This codemod helps automate the migration from `TextField` with `multiline` prop to the `Textarea` component. |
| 4 | + |
| 5 | +## Prerequisites |
| 6 | + |
| 7 | +- Node.js 14 or higher |
| 8 | +- jscodeshift installed globally or as a dev dependency |
| 9 | + |
| 10 | +```bash |
| 11 | +npm install -g jscodeshift |
| 12 | +# or |
| 13 | +pnpm add -D jscodeshift |
| 14 | +``` |
| 15 | + |
| 16 | +## Usage |
| 17 | + |
| 18 | +### Quick Start |
| 19 | + |
| 20 | +```bash |
| 21 | +# Run on a single file |
| 22 | +npx jscodeshift -t textfield-to-textarea.codemod.js src/components/MyComponent.tsx |
| 23 | + |
| 24 | +# Run on a directory |
| 25 | +npx jscodeshift -t textfield-to-textarea.codemod.js src/ |
| 26 | + |
| 27 | +# With TypeScript parser |
| 28 | +npx jscodeshift -t textfield-to-textarea.codemod.js --parser=tsx src/ |
| 29 | +``` |
| 30 | + |
| 31 | +### Options |
| 32 | + |
| 33 | +```bash |
| 34 | +# Dry run (see what would change without modifying files) |
| 35 | +npx jscodeshift -t textfield-to-textarea.codemod.js --dry src/ |
| 36 | + |
| 37 | +# Show diff |
| 38 | +npx jscodeshift -t textfield-to-textarea.codemod.js --print src/ |
| 39 | + |
| 40 | +# Verbose output |
| 41 | +npx jscodeshift -t textfield-to-textarea.codemod.js -v 2 src/ |
| 42 | +``` |
| 43 | + |
| 44 | +## What the Codemod Does |
| 45 | + |
| 46 | +1. **Finds TextField components** with `multiline` prop set to `true` |
| 47 | +2. **Replaces component name** from `TextField` to `Textarea` |
| 48 | +3. **Removes multiline prop** |
| 49 | +4. **Renames textareaRef to ref** (if present) |
| 50 | +5. **Updates imports** to include `Textarea` instead of (or in addition to) `TextField` |
| 51 | +6. **Removes TextField import** if no longer used in the file |
| 52 | + |
| 53 | +## Examples |
| 54 | + |
| 55 | +### Example 1: Basic Transformation |
| 56 | + |
| 57 | +**Before:** |
| 58 | +```tsx |
| 59 | +import { TextField } from '@equinor/eds-core-react' |
| 60 | + |
| 61 | +function MyComponent() { |
| 62 | + return ( |
| 63 | + <TextField |
| 64 | + multiline |
| 65 | + label="Description" |
| 66 | + placeholder="Enter text" |
| 67 | + /> |
| 68 | + ) |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +**After:** |
| 73 | +```tsx |
| 74 | +import { Textarea } from '@equinor/eds-core-react' |
| 75 | + |
| 76 | +function MyComponent() { |
| 77 | + return ( |
| 78 | + <Textarea |
| 79 | + label="Description" |
| 80 | + placeholder="Enter text" |
| 81 | + /> |
| 82 | + ) |
| 83 | +} |
| 84 | +``` |
| 85 | + |
| 86 | +### Example 2: With Refs |
| 87 | + |
| 88 | +**Before:** |
| 89 | +```tsx |
| 90 | +import { TextField } from '@equinor/eds-core-react' |
| 91 | +import { useRef } from 'react' |
| 92 | + |
| 93 | +function MyComponent() { |
| 94 | + const ref = useRef<HTMLTextAreaElement>(null) |
| 95 | + |
| 96 | + return ( |
| 97 | + <TextField |
| 98 | + multiline |
| 99 | + textareaRef={ref} |
| 100 | + label="Notes" |
| 101 | + /> |
| 102 | + ) |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +**After:** |
| 107 | +```tsx |
| 108 | +import { Textarea } from '@equinor/eds-core-react' |
| 109 | +import { useRef } from 'react' |
| 110 | + |
| 111 | +function MyComponent() { |
| 112 | + const ref = useRef<HTMLTextAreaElement>(null) |
| 113 | + |
| 114 | + return ( |
| 115 | + <Textarea |
| 116 | + ref={ref} |
| 117 | + label="Notes" |
| 118 | + /> |
| 119 | + ) |
| 120 | +} |
| 121 | +``` |
| 122 | + |
| 123 | +### Example 3: Mixed Usage (TextField kept for single-line) |
| 124 | + |
| 125 | +**Before:** |
| 126 | +```tsx |
| 127 | +import { TextField } from '@equinor/eds-core-react' |
| 128 | + |
| 129 | +function MyComponent() { |
| 130 | + return ( |
| 131 | + <> |
| 132 | + <TextField label="Name" /> |
| 133 | + <TextField multiline label="Description" /> |
| 134 | + </> |
| 135 | + ) |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +**After:** |
| 140 | +```tsx |
| 141 | +import { TextField, Textarea } from '@equinor/eds-core-react' |
| 142 | + |
| 143 | +function MyComponent() { |
| 144 | + return ( |
| 145 | + <> |
| 146 | + <TextField label="Name" /> |
| 147 | + <Textarea label="Description" /> |
| 148 | + </> |
| 149 | + ) |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +## Limitations |
| 154 | + |
| 155 | +The codemod handles most common cases but has limitations: |
| 156 | + |
| 157 | +### Not Handled Automatically |
| 158 | + |
| 159 | +1. **Conditional multiline prop:** |
| 160 | + ```tsx |
| 161 | + <TextField multiline={someCondition} /> |
| 162 | + ``` |
| 163 | + → Manual review needed |
| 164 | + |
| 165 | +2. **Dynamic prop spreading:** |
| 166 | + ```tsx |
| 167 | + const props = { multiline: true, label: "Text" } |
| 168 | + <TextField {...props} /> |
| 169 | + ``` |
| 170 | + → Manual review needed |
| 171 | + |
| 172 | +3. **Props not available on Textarea:** |
| 173 | + ```tsx |
| 174 | + <TextField multiline inputIcon={<Icon />} /> |
| 175 | + ``` |
| 176 | + → Manual refactoring needed (remove inputIcon) |
| 177 | + |
| 178 | +4. **Complex ref patterns:** |
| 179 | + ```tsx |
| 180 | + <TextField multiline ref={combineRefs(ref1, ref2)} /> |
| 181 | + ``` |
| 182 | + → Manual review needed |
| 183 | + |
| 184 | +## Manual Review Checklist |
| 185 | + |
| 186 | +After running the codemod, review the following: |
| 187 | + |
| 188 | +- [ ] Check for TextField components with conditional `multiline` props |
| 189 | +- [ ] Verify all Textarea components have correct props |
| 190 | +- [ ] Remove any Textarea-incompatible props (inputIcon, unit, meta) |
| 191 | +- [ ] Update TypeScript types if using TextFieldProps |
| 192 | +- [ ] Test all migrated components |
| 193 | +- [ ] Update tests if they reference TextField with multiline |
| 194 | +- [ ] Check console for any warnings |
| 195 | + |
| 196 | +## Prompts for Common Scenarios |
| 197 | + |
| 198 | +### Find all TextField with multiline in your codebase |
| 199 | + |
| 200 | +```bash |
| 201 | +# Using grep |
| 202 | +grep -r "TextField.*multiline" src/ |
| 203 | + |
| 204 | +# Using ripgrep (faster) |
| 205 | +rg "TextField.*multiline" src/ |
| 206 | + |
| 207 | +# Using ag (the silver searcher) |
| 208 | +ag "TextField.*multiline" src/ |
| 209 | +``` |
| 210 | + |
| 211 | +### Find potential missed cases |
| 212 | + |
| 213 | +```bash |
| 214 | +# Find TextField with multiline spread in props |
| 215 | +rg "TextField.*\{\.\.\..*\}" src/ |
| 216 | + |
| 217 | +# Find conditional multiline |
| 218 | +rg "multiline=\{" src/ |
| 219 | +``` |
| 220 | + |
| 221 | +## Testing the Codemod |
| 222 | + |
| 223 | +Test the codemod on sample files before running on your entire codebase: |
| 224 | + |
| 225 | +```bash |
| 226 | +# Create a test file |
| 227 | +cat > test-file.tsx << 'EOF' |
| 228 | +import { TextField } from '@equinor/eds-core-react' |
| 229 | +
|
| 230 | +export const TestComponent = () => ( |
| 231 | + <TextField multiline label="Test" /> |
| 232 | +) |
| 233 | +EOF |
| 234 | + |
| 235 | +# Run codemod |
| 236 | +npx jscodeshift -t textfield-to-textarea.codemod.js --dry test-file.tsx |
| 237 | + |
| 238 | +# Check the output |
| 239 | +cat test-file.tsx |
| 240 | +``` |
| 241 | + |
| 242 | +## Rollback |
| 243 | + |
| 244 | +If you need to rollback the changes: |
| 245 | + |
| 246 | +```bash |
| 247 | +# If you have git |
| 248 | +git checkout -- src/ |
| 249 | + |
| 250 | +# If you made a backup |
| 251 | +cp -r src.backup/* src/ |
| 252 | +``` |
| 253 | + |
| 254 | +## Support |
| 255 | + |
| 256 | +If you encounter issues with the codemod: |
| 257 | + |
| 258 | +1. Check the limitations section above |
| 259 | +2. Review the manual review checklist |
| 260 | +3. Try running with `-v 2` for verbose output |
| 261 | +4. Create an issue with a minimal reproduction case |
| 262 | + |
| 263 | +## Advanced Usage |
| 264 | + |
| 265 | +### Custom Parser Options |
| 266 | + |
| 267 | +```bash |
| 268 | +# For JavaScript files |
| 269 | +npx jscodeshift -t textfield-to-textarea.codemod.js --parser=babylon src/ |
| 270 | + |
| 271 | +# For Flow |
| 272 | +npx jscodeshift -t textfield-to-textarea.codemod.js --parser=flow src/ |
| 273 | +``` |
| 274 | + |
| 275 | +### Ignore Patterns |
| 276 | + |
| 277 | +```bash |
| 278 | +# Ignore specific directories |
| 279 | +npx jscodeshift -t textfield-to-textarea.codemod.js \ |
| 280 | + --ignore-pattern="**/node_modules/**" \ |
| 281 | + --ignore-pattern="**/build/**" \ |
| 282 | + src/ |
| 283 | +``` |
| 284 | + |
| 285 | +### Process Specific File Types |
| 286 | + |
| 287 | +```bash |
| 288 | +# Only .tsx files |
| 289 | +find src -name "*.tsx" -exec npx jscodeshift -t textfield-to-textarea.codemod.js {} \; |
| 290 | + |
| 291 | +# Both .tsx and .ts files |
| 292 | +npx jscodeshift -t textfield-to-textarea.codemod.js \ |
| 293 | + --extensions=tsx,ts \ |
| 294 | + src/ |
| 295 | +``` |
0 commit comments