vinhnx/codemods: The Rust Migration Repo That Thinks in Layers

A codemod registry that routes simple renames, context-heavy rewrites, and edge-case migrations into different tools, so breaking changes become repeatable work instead of one-off pain.

vinhnx/codemods · 12 min read · vinhnx/codemods

A wide customs-style sorting hall shows one incoming crate of source code split into three physical lanes. The scene explains the article's core idea: migrations are routed by complexity, from simple renames to structural rewrites to edge cases that need human judgment.
The repo's real product is not replacement text. It is judgment about which kind of migration each change deserves.

I built these codemods to automate some of the tedious refactoring tasks I encounter regularly. They are biased towards my own workflow, but I hope others find them useful too.

Vinh Nguyen, Creator & Maintainer · My Codemods Collection
Key Takeaways

Why breaking changes become migration debt

Most dependency upgrades are not dramatic. They are a pile of small edits that have to be right everywhere, or they become expensive cleanup later. That is the problem vinhnx/codemods is built to attack.

A breaking change is not just syntax churn. It is a trust problem. You need a migration that is deterministic enough to run across a codebase and selective enough to avoid touching the wrong spots.

The repo is a routing system, not a bucket of scripts

Each codemod lives in its own self-contained package, with its own workflow, rules, tests, scripts, and sometimes an AI skill. That structure is the point. The repo is saying that different kinds of migration work deserve different kinds of machinery.

codemods/
  axum-0-7-to-0-8/
    workflow.yaml
    rules/
    scripts/
    tests/
    agents/skill/
  clap-v3-to-v4/
    workflow.yaml
    rules/
    scripts/
    tests/
    agents/skill/

Simple changes stay declarative. Harder ones move into TypeScript, where the script can walk parents, children, ancestors, definitions, and references instead of guessing from raw text. The result is not just a search-and-replace pass. It is a structural rewrite with guard rails.

A hedcut-style portrait of Vinh Nguyen based on his GitHub avatar. It gives a human face to a repository that otherwise looks like pure tooling and shows who is making the migration decisions.

That bias is useful, not limiting. A migration registry gets better when it encodes a point of view, because each rule is a bet about where humans keep making the same mistake.

Simple renames, context-aware rewrites, long-tail migrations

The tiering is the whole design. YAML rules handle the obvious rename. TypeScript handles AST-aware logic when a change depends on context. The AI skill folders are the escape hatch for the long tail, where static rules are not enough.

The repository routes migration work by confidence level. Simple renames stay declarative, structural rewrites become scripts, and ambiguous cases fall through to skills.

A tight tabletop scene shows three tools arranged around a Rust code page: a punch stamp for a simple rename, a caliper for an AST-aware rewrite, and a notebook for a more ambiguous migration. The close-up explains why the repo uses different methods for different kinds of change.
The useful question is not whether a change can be automated. It is how much judgment the change needs.

The repo also uses guard functions so a method name only gets rewritten when the surrounding code says it belongs to the right crate. That prevents accidental matches in unrelated code that happens to look similar.

What the Rust examples reveal

The examples make the difference between cosmetic and semantic change easy to see. `clap` can collapse chained argument bounds into a cleaner range expression. `axum` can rewrite route parameters without mangling raw strings. `hyper`, `rand`, `tree-sitter`, and `ratatui` show the same pattern: this repo cares about meaning, not just spelling.

// before
.arg(
    Arg::new("items")
        .min_values(1)
        .max_values(5)
)

// after
.arg(
    Arg::new("items")
        .num_args(1..=5)
)

That is why the `tests/` folders matter so much. Every codemod ships input and expected output pairs, which turns the repo into a regression suite as much as a migration library.

Why this is more than find-and-replace

The cleanest comparison is layered. Engines like ast-grep and jscodeshift provide the machinery. vinhnx/codemods provides the migration content and the judgment about which layer should handle each change.

ApproachRoleWhat you getWhat you still need
Hand editManual migrationMaximum judgmentTime, consistency, repeatability
ast-grep or jscodeshiftCodemod engineStructural matching and editsAuthored transforms
vinhnx/codemodsCurated migration registryPrebuilt Rust-specific judgmentAn engine to execute the transforms

That separation is the real leverage. A team does not have to rediscover its migration strategy every time a dependency breaks. It can reuse a strategy that has already been encoded, tested, and narrowed down to the cases that matter.