Large-scale refactoring — renaming a widely-used interface, migrating from one library to another, or changing a convention across hundreds of files — is where Claude Code shines. Unlike IDE refactoring tools, Claude Code understands intent: it can transform complex patterns, handle edge cases, and verify its own changes using your test suite.
This guide covers large-scale refactoring with Claude Code: systematic transformations, AST-based codemods, migration strategies, and safety verification.
Scoping a Refactor
I want to migrate from the old callback-based analytics library
to the new promise-based one. There are 120+ files using it.
How should I approach this systematically?
Before writing any code, map the scope:
1. Show me all the different ways analytics.track() is called across the codebase
2. List the most common call patterns (sorted by frequency)
3. Identify edge cases that don't fit the common pattern
Claude Code searches the entire codebase and produces:
Found 247 calls across 83 files. Patterns:
1. analytics.track('event', { props }) — 198 occurrences (80%)
2. analytics.track('event') — 31 occurrences (12%)
3. analytics.track('event', null, callback) — 18 occurrences (7%)
Edge cases (1% each):
- Dynamic event names: analytics.track(eventName, props)
- Conditional tracking: if (condition) analytics.track(...)
Now write the migration one pattern at a time, starting with the most common.
AST-Based Codemods with jscodeshift
Write a jscodeshift codemod that converts the old callback-based
analytics.track() calls to the new promise-based API.
Old: analytics.track('event', props, callback)
New: await analytics.track('event', props)
// codemods/migrate-analytics.ts
import type { Transform, ASTPath, CallExpression } from 'jscodeshift';
const transform: Transform = (file, api, options) => {
const j = api.jscodeshift;
const root = j(file.source);
let modified = false;
// Find all analytics.track() calls
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
object: { name: 'analytics' },
property: { name: 'track' },
},
})
.forEach((path: ASTPath<CallExpression>) => {
const args = path.node.arguments;
const hasCallback = args.length === 3 &&
(args[2].type === 'FunctionExpression' || args[2].type === 'ArrowFunctionExpression');
if (hasCallback) {
// analytics.track('event', props, callback)
// → await analytics.track('event', props).then(callback)
const newCall = j.awaitExpression(
j.callExpression(
j.memberExpression(
j.callExpression(
j.memberExpression(j.identifier('analytics'), j.identifier('track')),
[args[0], args[1]], // event and props only
),
j.identifier('then'),
),
[args[2]], // callback
),
);
j(path).replaceWith(newCall);
modified = true;
} else if (args.length === 2 || args.length === 1) {
// analytics.track('event', props) → await analytics.track('event', props)
const newCall = j.awaitExpression(
j.callExpression(path.node.callee, args),
);
j(path).replaceWith(newCall);
modified = true;
}
// Make parent function async if it isn't already
ensureFunctionIsAsync(j, path);
});
return modified ? root.toSource({ quote: 'single' }) : file.source;
};
function ensureFunctionIsAsync(j: any, path: ASTPath<any>) {
// Walk up to find the nearest function
let current = path.parent;
while (current) {
const node = current.node;
if (
node.type === 'FunctionDeclaration' ||
node.type === 'FunctionExpression' ||
node.type === 'ArrowFunctionExpression'
) {
if (!node.async) {
node.async = true;
}
break;
}
current = current.parent;
}
}
export default transform;
module.exports = transform; // jscodeshift requires CommonJS export
Run the codemod:
# Dry run first — preview changes without modifying files
npx jscodeshift -t codemods/migrate-analytics.ts src/ --dry --print
# Apply to all TypeScript files
npx jscodeshift -t codemods/migrate-analytics.ts src/ --extensions=ts,tsx
# Review the diff
git diff --stat
Claude Code Prompts for Systematic Refactors
Rename the UserProfile interface to Profile.
Update all usages, imports, and re-exports. Start with the type definition,
then update each file. Show me what you changed in each file.
The sequential approach matters — by starting with the type definition, Claude Code can verify each subsequent file update against the new definition.
I'm migrating from Enzyme to Testing Library.
Here's an example Enzyme test (paste test).
Convert it to Testing Library idioms.
Don't just change the syntax — apply Testing Library principles:
test behavior, not implementation.
The following function has grown to 200 lines with 8 parameters.
Identify the distinct responsibilities and refactor into smaller functions.
Maintain the exact same behavior — write tests for the current behavior first,
then refactor so the tests still pass.
Incremental Migration
We have 300 files using the old Date library.
We can't migrate everything at once. Show me how to do it
file-by-file with coexistence during the transition.
// Pattern: adapter that supports both APIs during migration
// src/utils/date-compat.ts
import { format as oldFormat, parse as oldParse } from 'old-date-lib';
import { format as newFormat, parse as newParse } from 'date-fns';
// Adapter: same interface as old library, uses new library under the hood
export const formatDate = (date: Date, formatStr: string): string => {
// Map old format tokens to date-fns tokens
const mapped = formatStr
.replace(/YYYY/g, 'yyyy')
.replace(/DD/g, 'dd')
.replace(/d/g, 'EEE');
return newFormat(date, mapped);
};
// Mark migrated files with a comment — track progress
// @migrated-date-lib — grep for this to find unmigrated files
Track progress:
# Count migrated vs unmigrated files
echo "Migrated: $(grep -r '@migrated-date-lib' src/ | wc -l)"
echo "Remaining: $(grep -r 'old-date-lib' src/ | wc -l)"
Verification Strategy
After any large refactor:
The refactor is complete. Now verify it:
1. Run the full test suite — show me any failures
2. Find files I might have missed (grep for old patterns)
3. Check that TypeScript compiles with no errors
4. Run a sample of the changed files and explain what changed to verify correctness
# Verification commands Claude Code can run post-refactor:
npm test -- --coverage && echo "Tests passed"
grep -r "analytics\.track" src/ | grep -v "await" | head -20 # Find async calls that weren't migrated
npx tsc --noEmit # Type check without emitting
git diff --stat | tail -5 # Summary of changes
For the testing patterns that create a safety net before refactoring, see the testing guide. For refactoring React components specifically — moving from class to functional, Enzyme to Testing Library, see the React guide. The Claude Skills 360 bundle includes refactoring skill sets for systematic migrations, codemods, and large-scale transformations. Start with the free tier to try refactoring code generation.