AI refac
Languages

بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ

TypeScript / JavaScript

How refac moves TypeScript and JavaScript files and folders and renames symbols.

TypeScript and JavaScript have two operations with two engines. File and folder moves use the Oxc parser and the TypeScript module resolver in a Bun script, without building a compiler Program. Symbol renames use the TypeScript 7 native language server, because renaming needs the type checker.

Extensions: .ts .tsx .js .jsx .mts .cts .mjs .cjs.

Required tooling

ToolNotes
bunFound in PATH, then at ~/.bun/bin/bun
Locked helper dependenciesInstalled on the first run with bun install --frozen-lockfile: the Oxc parser and resolver, TypeScript 6 for moves, and TypeScript 7 native for rename
tsconfig.jsonDecides which files are callers, so it must cover every local file that takes part in imports

Point --project-path at the package that owns the authoritative tsconfig.json. Its include or files configuration must cover all local TypeScript and JavaScript files that import each other, and then every caller will inshallah be found. External packages such as React stay package imports and do not need to be included. Without a tsconfig.json, TypeScript's default file discovery is used with allowJs and bundler resolution.

Moves

refac move \
  --project-path /path/to/package \
  --source-path src/lib/util.ts \
  --target-path src/core/util.ts

A folder works the same way, with the folder as source and target. The report looks like this:

// Alhamdulillah 1 requested path was successfully refactored:

// Typescript results:

// 1 TypeScript/JavaScript source file moved (limit: 30).

src/lib/util.ts -> src/core/util.ts

// Markdown links to the moved files:

// Note: Checked 0 Markdown files; updated 0 links in 0 files.

What is updated

Imports, re-exports, side-effect imports, literal dynamic imports, CommonJS require, TypeScript import-equals, import types, and resolved module augmentations. Node ESM and CommonJS conditions and explicit resolution-mode attributes follow TypeScript's resolver. Asset imports keep their query and fragment suffixes. Markdown links to the moved files follow too, in the same command.

Path aliases declared in compilerOptions.paths keep their spelling when the new location fits the same alias. A fixed alias, or a move out of an alias, becomes an explicit relative import. The paths mappings themselves are never edited. After all moves, every rewritten specifier is resolved again against the files as they will be, and a specifier that would stop resolving makes the command fail instead of report success.

Batch limit and workflow

Each call may contain at most 30 source files. Files inside a folder count, and refac measures the request before it changes anything. A request over the limit fails without mutation, and the error tells the measured number. Successful output reports the count.

For each batch:

  1. Stop duplicate dev or build watchers.
  2. Keep the batch at 30 source files or fewer.
  3. Search the repository for the old paths and inspect the diff.
  4. Run the package type-check or the production build before the next batch. It should inshallah stay green.

Preview

--dry-run builds the whole plan, every moved file and every rewritten specifier, and prints it. The check that each rewritten specifier resolves from its new place also runs, against a model of the files as they will be after the move. An import of an asset through an alias can only be checked by the real move, so a dry run counts it in a note.

Limitations

Existing target. A target path that already exists is refused with Move target already exists.

Full-project scan. Every configured source is scanned, so a one-file move still reads the whole tsconfig project. Narrow --project-path for very large workspaces.

Process limits. The helper is stopped after 5 minutes or when its memory exceeds 4 GiB. REFAC_TYPESCRIPT_MAX_RSS_MB changes the memory threshold. Inspect the working tree after such a stop, because a terminated process can interrupt a rollback.

Project references. Moves are rejected in projects that use TypeScript project references.

Refused requests. Malformed sources or configuration, symlinked move paths, overlapping requests, locally rebound require calls, and outgoing relative imports that do not resolve all fail with a message.

Not rewritten. Aliases that are not in the tsconfig, computed module paths, arbitrary strings that happen to contain a path, JSDoc and triple-slash comments, framework path conventions, and package or configuration metadata. Search for the old paths and run your type-check or build, and both should inshallah come back clean.

Symbol rename

refac rename \
  --project-path /path/to/package \
  --file src/lib/util.ts \
  --symbol total \
  --new-name grandTotal

refac renames one variable, function, class, interface, enum, or member and every reference the type system links to it: named, default, aliased, and namespace imports, re-exports, class members, JSX components, and .js files. Strings, comments, JSON, and Markdown are never edited. TypeScript keeps public names, so { total } becomes { total: grandTotal } and export { total } from "./x" becomes export { grandTotal as total } from "./x".

FlagMeaning
--fileA file that contains the symbol, as declaration or as any usage
--symbol, --new-nameThe current and the new identifier
--line, --columnThe 1-based position that picks one symbol when the name means several in the file. The command lists the candidates when it is ambiguous
--dry-runPlan and verify, report the edits, change no file
--batch FILESeveral renames in one session, all or nothing

Safety

Nothing is written until the whole rename is planned and verified in memory. TypeScript's own rename does not notice a new name that already exists in scope, so refac asks the engine for the references of the renamed declaration and requires exactly one reference spelled with the new name per planned edit. A name clash, or a name that shadows another symbol, changes that count and the rename is refused without touching a file. Files are read again before the write, and a failed write restores the files already written, so a half-finished rename inshallah won't stay on your disk.

Limitations

TypeScript 7 configuration. A tsconfig that uses options TypeScript 7 removed, such as baseUrl or moduleResolution: node10, is rejected with the engine's diagnostic, because the engine would otherwise miss files without an error. File moves are not affected.

Coverage. Only usages in projects the engine loads are renamed. Put every caller in the package's tsconfig, then search for the old name and run the type-check. Edits that would fall outside --project-path, for example in a referencing project, stop the rename.

Hard failures. A missing tsconfig, a file outside the tsconfig, a symbol from node_modules or the standard library, an ambiguous name, a reserved word as the new name, a timeout of 5 minutes, or engine memory above 4 GiB all stop the command with a message and leave every file unchanged.

On this page