Enhanced Package Prefixes
What happens when you combine one obscure IntelliJ IDEA feature with two obscure Scala features? If you choose the right ones, their obscurities just might cancel each other out, so that instead making everything far more opaque you can get an intuitive and internally consistent system.
Let’s see how the upcoming version of the Scala plugin combines package prefixes with chained package clauses and relative imports.
The problem with packages
Both Java and Scala use reverse domain name notation for packages. According to the Scala Style Guide, you should write
package org.example.application rather than
package application, which is good for eliminating namespace collisions. However, reverse domain notation has a few downsides.
The first problem is that you have to repeat the prefix everywhere in your own project. For example, you have to write
import org.example.application.model.Person instead of
The second problem is that, according to another best practice, you must maintain a 1-to-1 relationship between packages and directories, so that
org.example.application.model.Person must reside in
application are superfluous intermediate directories. In this way, the package prefix becomes a directory prefix, which creeps into VCS paths, build scripts, and file managers.
IntelliJ IDEA can partially address this problem by flattening packages in the Project View:
This is nice but far from perfect. First, it still shows an intermediate node. Second, it does nothing for VCS paths, build scripts, or file managers, let alone prefixes in the source code.
Another approach is to abandon the reverse domain name convention. (That’s probably why we have packages such as
munit.) This can improve VCS paths, build scripts, and file managers, but it requires a prefix everywhere (even if it’s only a short one). Furthermore, it goes against the Scala code style.
But it turns out we can have our cake and eat it too. Here’s how.
The first feature we’ll use is IntelliJ IDEA’s package prefix. You might not be aware of this setting, which is available in Project Structure | Modules | Sources if you invoke Edit Properties on a source folder:
A source folder with a package prefix is displayed in the Project View like so:
src has a package prefix
org.example.application, IntelliJ IDEA treats that directory as
src/org/example/application. (So, the package prefix is actually a directory prefix but is expressed in terms of the
. notation rather than the
/ notation.) All refactorings and inspections respect this setting.
To make the setting more accessible, we’ve added a Package Prefix field to the Project Wizard:
We’ve also added an Edit Package Prefix entry to the context menu:
In addition, sbt-idea-settings now offers an
idePackagePrefix key, so you can easily configure package prefixes in an sbt project:
Now there are no intermediate nodes in the Project View and no superfluous directories in VCS paths, build scripts, or file managers. So far so good. But this only fixes directories, not packages — in your code, you still have to write
import org.example.application.model.Person. Furthermore, because that class now resides in
model/Person.scala, this breaks the 1-to-1 correspondence between directories and packages (at least visually). That’s probably why this feature of IntelliJ IDEA is relatively unknown.
Fortunately, in Scala, we have a few tricks that complement the package prefix and restore the symmetry.
It’s no secret that Scala lets you chain package clauses. For example, instead of
package foo.bar, you can write
package foo; package bar. Arbitrary chaining, however, is hardly a good idea, because chaining brings all package members into scope and is equivalent to using a wildcard import:
package foo.bar; import foo._. The following makes much more sense:
We call a package such as
org.example.application a base package. The Scala plugin can take a base package into account and automatically chain package declarations at a given point. Now you can refer to classes in your project without having to repeat the same prefix. So, a base package does for packages what a package prefix does for directories.
The Scala plugin has supported package prefixes for a long time. There’s a Base Package tab in Settings | Language | Scala. There is a
basePackages key in the
sbt-idea-settings plugin. However, the problem is that base packages are also a partial solution that fixes packages but not directories. Base packages also break the symmetry between packages and directories, albeit in reverse.
Luckily, everything falls into place once we combine package prefixes with base packages. That’s why base packages are now inherited from package prefixes by default:
You can still configure base packages separately, but we recommend using package prefixes as a starting point.
There’s just one more thing. It’s not enough to automatically chain package declarations — you also want the IDE to use relative imports (but only relative to a base package); otherwise, there’s just no sense in bothering with the chaining. Even though the Scala plugin had base packages, it couldn’t perform selective relative imports. Now it can:
Notice how there’s now an intuitive symmetry between the two representations. This approach is even cleaner than using a non-standard single-word package, such as
cats. And we’re doing all that with (code) style!
By default, relative imports are enabled only for base packages (and base packages are inherited from package prefixes):
So from the user’s point of view, everything should just work as long as you’ve configured a package prefix. At the same time, you can configure each of the features manually as you see fit. We’ve also updated the default import layout:
scala* imports now go together, so that imports have clear "project classes", "other classes", and "standard library" groups.
If you create a new project and specify a package prefix, everything should work automatically. However, if you want to use package prefixes in an existing project, you need to do the following (adjust directory and package names accordingly):
Move files from
src. Don’t use the Move Refactoring for that, because it affects the package declarations. If you use Git, consider using
git mvrather than just
org.example.applicationpackage prefix for the source folder. In an IntelliJ IDEA project, use the Edit Package Prefix in the Project View context menu. In an sbt project, insert (or update)
addSbtPlugin("org.jetbrains" % "sbt-ide-settings" % "1.1.0")in
idePackagePrefix = "org.example.application"to
build.sbt, and reload the project in IntelliJ IDEA.
Run the "Base package declaration" inspection to chain package declarations. Run Find Action (Ctrl+Shift+A), find "Run Inspection By Name" (Ctrl+Alt+Shift+I), run "Base package inspection", and then apply the quick-fix to all the occurrences.
Optimize the imports. Select a directory (or the whole project) in the Project View and invoke Code | Optimize Imports (Ctrl+Alt+O).
Granted, the procedure is somewhat complicated, but you only need to perform it once to enjoy the benefits forever. In the future, we may implement an automatic migration to make the transition easier.
To try this now, select Early Access Program (or Nightly builds) in Settings | Languages | Scala | Updates | Channel (you can revert to a more stable build in the same way at any time).
We’re announcing this feature early, before the official release, in order to gather more information and polish off the rough edges. We need your feedback! Please report any bugs to YouTrack. If you have any questions, feel free to ask them in our Gitter.