Files
scriptc/scripts/split/README.md
T
Chris Tate 2b657e6e3c Format the split tools' output with the tools checkout's oxfmt configuration
The split tools ran oxfmt in the checkout they rewrite. oxfmt looks up
its configuration from that directory and from the formatted files'
directories, and runs an oxfmt.config.ts it finds there, so a branch that
rehome-split rewrites could run code with the maintainer's permissions.
The tools now format through format.mjs, which passes the tools
checkout's .oxfmtrc.jsonc with --config and disables the nested lookup,
so no configuration file of the rewritten checkout is read or run. With
no files to format it does nothing, where oxfmt would format the whole
directory.

The split-tools tests cover split-methods and move-state on a checkout
whose root and src directory commit an oxfmt.config.ts that records it
ran.
2026-10-22 11:12:02 -05:00

2.7 KiB

Split tools

These scripts split a large source file into smaller files without changing behavior, and carry open branches across the split. Each transform is deterministic: the same config gives the same result on any version of the tree, and rehoming depends on that.

Run them from a checkout with node_modules installed. That checkout is the tools directory (--tools) and supplies typescript and oxfmt.

Transforms

  • split-module.mjs plan|apply <config> moves top-level declarations into new files. plan computes a manifest from seed declarations, and apply does the move.
  • split-methods.mjs apply <config> moves class methods into exported functions that take the instance as a parameter (emitter). The class keeps a one-line stub with the same signature, so callers don't change.
  • move-state.mjs apply <config> moves a group of class fields into a feature's state object, defined in the feature's file with defineModuleState (packages/compiler/src/backend/llvm/emitter-state.ts). It rewrites this.<field> and emitter.<field> to KEY.of(this).<field> and creates files from templates. It also applies the change's other edits as literal replacements, in order. A replacement whose text isn't found the expected number of times is reported and skipped.

Each transform takes --repo <dir> (the tree to rewrite) and --tools <dir>, and formats what it writes with the tools checkout's oxfmt and its .oxfmtrc.jsonc (format.mjs). The formatter reads no configuration from --repo, so an oxfmt.config.ts a branch commits is never run. The tools read and write the tree's files only at real paths inside --repo, and a config's templates and manifest only inside the config's directory. A path that resolves elsewhere, through a symlink or .., stops the tool with an error (confine.mjs). configs/ holds the configs of splits that have landed. For example, configs/loop-state/ moves the loop optimizations' state out of LlEmitter.

Rehoming an open branch

git fetch origin pull/<n>/head:pr-<n>
node scripts/split/rehome-split.mjs --branch pr-<n> --onto origin/main --config scripts/split/configs/<split>/<config>.json

The result is the branch pr-<n>-rehomed, a merge of origin/main into the branch.

The script applies the split's transform to the branch's merge base and to its tip, then cherry-picks the difference onto --onto. It records the result as a merge commit with parents (branch tip, --onto), so it can be pushed without rewriting the branch; --squash makes one ordinary commit instead. Conflicts made only of import lines in the split's files resolve to both sides' imports. Any other conflict stops the script, which lists the files and leaves its scratch worktree for you to resolve.