بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ
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
| Tool | Notes |
|---|---|
bun | Found in PATH, then at ~/.bun/bin/bun |
| Locked helper dependencies | Installed 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.json | Decides 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.tsA 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:
- Stop duplicate dev or build watchers.
- Keep the batch at 30 source files or fewer.
- Search the repository for the old paths and inspect the diff.
- 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 grandTotalrefac 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".
| Flag | Meaning |
|---|---|
--file | A file that contains the symbol, as declaration or as any usage |
--symbol, --new-name | The current and the new identifier |
--line, --column | The 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-run | Plan and verify, report the edits, change no file |
--batch FILE | Several 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.