launch-quickly
Decisions

0007 — Codemods emit text edits, never a reprinted AST

A two-line change buried in a nine-hundred-line reformat is a diff nobody can review, and a diff nobody can review is a diff nobody trusts.

Context

When a release breaks an API, someone has to rewrite customer code. The standard technique is parse → transform the AST → print.

Decision

A codemod produces text edits with byte offsets, not a rewritten file. Matches are located with the TypeScript parser; edits are applied by offset.

the obvious approach — parse, transform the AST, print — reformats everything it touches. A customer who upgrades and finds a two-line change buried in a nine-hundred line reformat cannot review it, and a diff nobody can review is a diff nobody trusts.

Locating with a real parser and editing with offsets gives both halves: from '@/lib/errors' appears inside strings, comments and template literals in a real codebase, and a regex that rewrites those produces a file that still parses and is subtly wrong. Every line the codemod did not care about is byte-identical afterwards.

Consequences

Edits apply back-to-front. Doing it forwards and adjusting positions as you go is the same computation with an accumulator that is wrong the first time somebody adds a case.

Overlapping edits throw rather than resolve. Silently applying both produces mangled source that still parses often enough to reach a customer.

The registry shipped empty, honestly. Registering a made-up codemod to prove the pipeline works would be testing against imagined inputs — the failure this project keeps catching in its own work. The engine was tested with fixtures instead, so the first real codemod was a few lines and a test rather than a design exercise.

The first real one, and what it declines to do

design/use-format-helpers rewrites x.toLocaleDateString() to formatDate(x) and adds the import. It deliberately leaves toLocaleString() alone: on a Date that renders date-and-time, on a number a formatted number, and without type information a codemod cannot tell which. Rewriting a Date to formatNumber would produce plausible garbage — the worst outcome an automatic edit can have, because plausible garbage does not get re-read.

Four of its six fixture cases assert that it declines. That ratio is the point: a codemod that is right ninety percent of the time is worse than one that refuses, because nobody re-reads the ninety.

On this page