بِسْمِ ٱللَّهِ ٱلرَّحْمَـٰنِ ٱلرَّحِيمِ
Moves
What a Kotlin move rewrites, what it refuses, and what the output tells you.
refac move on .kt files, or on a folder that contains .kt files, moves them and updates the package line, every import, and the usages that relied on sharing a package. Java callers of the moved Kotlin classes are updated too. The Kotlin language server computes the text edits by running IntelliJ's own move refactoring in memory, and refac applies them together with the file moves.
This page shows you what a request may be, what follows a move, and how to read the result.
What a request may be
- Files and folders. A
.ktfile or a folder, inside a source rootsrc/<source set>/kotlinorsrc/<source set>/java. The server derives the new package from the target folder, so a target outside a source root is refused. - Move, rename, or both. A pure move keeps the file name and a rename keeps the folder. A move to a new folder with a new name is done as two steps, move and then rename, because the server cannot do both at once. That holds for a folder too (
utiltolib/helpers), and the output says when it happened. - Renaming a file renames its class. Renaming
Helper.kttoUtils.ktalso renames the top-level typeHelpertoUtilswhen the file declares a type named like the file, and every usage follows. That is how the IDE behaves. refac checks that it happened and says so in the notes. A file without a type of that name is only renamed. - Folders. A package folder moves with its files and every subpackage.
- Several requests in one call. Requests are grouped by kind and target folder, with one server request per group inside one server session. They must not overlap: no source inside another source, no duplicate target, and no target that is another request's source.
Refused before anything starts
Each of these fails before the server is started and changes nothing.
- A
.javafile, or a folder that contains.javafiles. The server moves Java files without updating their package line, so refac refuses instead of leaving the project inconsistent. Move the Kotlin files, or use a Java-aware tool for Java. - A source or target that is not inside a source root, or that is a source root itself.
- A source and a target in different modules or source sets (
src/mainagainstsrc/test,commonMainagainstjvmMain). No package edit can fix what the code is then allowed to see. - A target that already exists, a target that keeps no
.ktextension, a..in a target path, and a move into itself.
What follows a move
The package line, every import, and every usage that needed no import before. Code in the same package needed no import, so after a move those usages gain one.
Imports for the names that stayed behind
A Kotlin file sees every top-level declaration of its own package without an import, so a file that moves to another package loses them. The server adds the imports it can resolve. refac then adds import <old package>.<name> for each name that
- the moved file uses outside comments and strings (inside
$nameand${...}templates too), - a file of the old package declares at the top level and not
private, and - the moved file does not import already. An explicit import of another
Helper, or an alias, is left alone because a second one would clash.
A name is only taken from the same Gradle module and from a source set the moved file can see. A type that a moved file declares itself is not imported. It looks at names, so overloads, expect, and actual are no problem. Each file that got imports is named in the notes, and the moved files should inshallah compile without an edit by hand.
Operators and delegates
Some calls need an import without writing the name: a[i] and a[i] = v (get, set), for (x in y) (iterator, hasNext, next), val (a, b) = p (componentN), and val n by d (getValue, setValue, provideDelegate). The server misses these. refac finds those uses in the moved file and adds the import when
- the declaration is an
operatorextension of the old package, and - the file writes the receiver type.
list[0]orby lazyin a file does not import an unrelatedgetorgetValueof the package.
It works in both directions. The files that stay behind get the import of an operator that moved away. An operator of another Gradle module is imported for a direct project dependency (project(":lib") or projects.lib) only, and api chains are not followed.
The server also writes some operators as calls when it adds an import, for example a < b as a compareTo b, which does not compile for a non-infix function. refac puts the written syntax back and keeps the import.
Only the package line and the imports change
The server tidies the files it edits. It deletes imports it takes for unused and it can re-wrap code. A move is not for that, so refac undoes it:
- An unused import stays, unless it names a class or function that a moved file declared. That import is rewritten to the new package, as for a used one.
- A change that only moves blanks and line breaks below the import lines is undone.
Checks after the server answers
The server is trusted for the reference updates, and refac checks what is cheap to check. A moved file's package line must now follow its folder, and a file renamed in place must have taken the class that carried its name. A server answer that does nothing shows up as a failed check and not as a quiet success. A file whose package line did not follow its folder before the move cannot be compared, and the notes say it was not checked.
Preview
--dry-run runs the real move on a throw-away copy of the project and reports the difference to the original: the moves, and per file the number of changed passages. The copy holds what .gitignore leaves in, plus local.properties and gradle/wrapper, up to 500 MiB (REFAC_DRY_RUN_COPY_MAX_MB). The server imports the copy with Gradle, so a dry run takes as long as a real move, about 40 seconds on a small project. Every path must lie inside the project.
A dry run is for the plan, not a cheap check. The real run starts the server again.
All or nothing
Every write and every move goes through an undo log. When anything fails, whether a server error, a failed check, or a write error, the log restores every edited file and every moved path, so a failed move inshallah won't leave a half-moved project. The error says that the move was undone and then gives the cause:
Kotlin — The move failed and every change was undone: Moving a -> b: <reason>The whole chain of causes is printed. A rollback that fails itself is reported with the failure and never hidden. When the language server itself dies, for example an out-of-memory kill or a JVM crash, the cause names how the process ended (the exit status, or the signal, where 9 is usually the Linux out-of-memory killer) and the last lines the server wrote to stderr.
What the output tells you
Besides the list of moves, refac move prints // Note: lines for things only you can act on:
- The split of a move and a rename into two steps.
- Android class names that were rewritten in XML and
RorBuildConfigimports that were added. See Android and Multiplatform. - The files whose content the move changes:
This move changes the content of N files: a, b, .... Paths are project-relative and at most 40 are named. It answers the question what did refac touch: a change in any other file ofgit diffis not from the move. - Old class names that still appear in files refac does not edit, with the file, the old name, and the new name, for example
build.gradle.kts still mentions com.example.app.MainKt (now com.example.launch.MainKt). Search for these and fix them by hand.
After a move, run the project's Gradle build. It should inshallah pass.