Best Practices

Learn how to write effective codemods by following best practices.


What is a Codemod? A codemod is an automated script used to refactor large codebases efficiently. It applies consistent transformations across multiple files, ensuring accuracy and reducing manual effort. Codemods are particularly useful for:

  • API migrations
  • Code deprecations
  • Style and pattern enforcement
  • Large-scale refactors

If you've never written a codemod before, check out the Your First Codemod guide.

Why Best Practices Matter

Writing effective codemods ensures:

  • Reviewability: Changes are clear, focused and can be reviewed easily.
  • Debuggability: Isolating transformations helps in identifying issues.
  • Safety: Reducing unintended side effects prevents breaking changes.
  • Maintainability: Code remains readable and easy to update.

Planning Your Codemod

Define the Transformation Goal

Before writing a codemod, clearly define:

  • What code pattern needs to change?
  • Why is this change necessary?
  • How can the transformation be validated?

Examples:

  • Goal: Convert componentWillReceiveProps lifecycle method to getDerivedStateFromProps in React class components.
  • Goal: Migrate from lodash to native JavaScript methods for performance improvements.
  • Goal: Upgrade React v18 to v19

Understand the Existing Codebase

Analyze code patterns before implementation:

  • Identify common variations in how a feature is used.
  • Ensure that special cases and edge cases are handled.
  • Run static analysis tools to gather metrics on affected files.

Writing Effective Codemods

Make Atomic Changes

Each codemod should focus on a single transformation. This simplifies debugging and enables safer incremental application.

Example (Converting var to const):

function transform(fileInfo, api) {
  const j = api.jscodeshift;

  return j(fileInfo.source)
    .findVariableDeclarators()
    .filter(path => path.parent.value.kind === "var")
    .forEach(path => path.parent.value.kind = "const")
    .toSource();
};

Avoid Unnecessary Modifications

Ensure that only required transformations are applied. Avoid whitespace or formatting changes unless essential.

Example (Updating function calls without affecting spacing):

.find(j.CallExpression, { callee: { name: "oldMethod" } })
.replaceWith(path => j.callExpression(j.identifier("newMethod"), path.value.arguments));

Minimize Breaking Changes

When writing codemods, preserve existing behavior and avoid unintended side effects.

Example (Avoiding breakage in optional arguments):

.find(j.CallExpression, { callee: { name: "fetchData" } })
.replaceWith(path => {
  const [url, options] = path.value.arguments;
  return j.callExpression(j.identifier("fetchDataV2"), [url, options || j.objectExpression([])]);
});

Use Test-Driven Development (TDD)

Write tests before implementing the codemod to validate expected output.

Example (Using Jest to test a jscodeshift transform):

const defineTest = require("jscodeshift/dist/testUtils").defineTest;
defineTest(__dirname, "convert-var-to-const", null, "basic");


Implementation Best Practices

Use a Robust AST-Based Tool

Leverage Abstract Syntax Tree (AST) tools to precisely manipulate code:

  • jscodeshift – Best for JavaScript and TypeScript codemods.
  • ast-grep – A powerful pattern-matching tool for structured code changes.
  • Babel Parser – Used internally by many JavaScript transformation tools.

Write Idempotent Codemods

Codemods should be safe to run multiple times without changing already transformed code.

Example (Checking for existing changes before modifying):

.find(j.CallExpression, { callee: { name: "fetchDataV2" } })
.filter(path => path.value.callee.name !== "fetchDataV2")
.replaceWith(...);

Support Dry Runs & Rollbacks

  • Allow previewing changes before applying them (--dry flag in jscodeshift).
  • Store backups of modified files.
  • Keep changes reversible by using version control effectively.

Composability of Codemods

Codemods should be designed to compose together for complex refactors.

Chaining Multiple Transformations

Instead of writing large monolithic codemods, break them into smaller ones that can be combined.

Example (Chaining two separate transforms):

jscodeshift -t transform1.js src && jscodeshift -t transform2.js src

Example (Piping transformations in a single script):

const transform = (fileInfo, api) => {
  return transform2({ source: transform1(fileInfo, api) }, api);
};

Using Middleware Approaches

  • Pass AST modifications through sequentially applied transforms.
  • Use separate transforms for renaming variables, updating method signatures, and adjusting imports.

Validating and Deploying Codemods

Manually Test on a Small Subset

Before applying codemods at scale:

  • Test against a few selected files.
  • Verify correctness with unit tests.
  • Review output diffs manually.

Run Against a Large Codebase in Phases

  • Apply codemods to non-critical files first.
  • Gradually expand scope based on validation.
  • Monitor for regressions.

Monitor and Collect Feedback

  • Capture logs of changed files.
  • Gather developer feedback on the changes.
  • Iterate and refine codemods based on findings.

Conclusion

Key Takeaways

  • Write atomic, idempotent codemods.
  • Use AST-based tools for accurate transformations.
  • Test codemods before deployment.
  • Ensure composability for complex migrations.

By following these best practices, you can develop robust, maintainable codemods that simplify large-scale refactoring while minimizing risk.


Further Reading