Scala Tooling
A comprehensive guide to Scala development tools. Configuration best practices for build systems, compiler flags, Scalafix, Scalafmt, Scalastyle, and Wartremover.
Categories
Build [HIGH]
Configure sbt and Mill builds for correct module layout, cross-building, and centralized dependency versions.
| Rule | Description |
|---|---|
| build-cross-scala-version-support | Cross-build with crossScalaVersions and the + command, not duplicated modules |
| build-dependency-version-management | Centralize dependency versions in project/Dependencies.scala |
| build-mill-build-definition | Define Mill modules with ScalaModule and moduleDeps |
| build-sbt-multi-module-layout | Structure a multi-module sbt build with aggregate and dependsOn |
Compiler Flags [HIGH]
Turn on the Scala compiler's warning and strictness flags deliberately, and gate CI on them without an all-or-nothing switch.
| Rule | Description |
|---|---|
| compiler-flags-deprecation-and-feature-warnings | Always enable -deprecation and -feature, not just the warning count |
| compiler-flags-scala3-strict-mode | Assemble a strict Scala 3 flag profile beyond the defaults |
| compiler-flags-unused-imports-error | Fail the build on unused imports with -Wunused, not the retired 2.13 flag name |
| compiler-flags-xfatal-warnings | Pair -Xfatal-warnings with -Wconf instead of an all-or-nothing switch |
Scalafix [HIGH]
Run Scalafix's syntactic and semantic rules in CI check mode to keep the codebase free of unused code and stale patterns.
| Rule | Description |
|---|---|
| scalafix-ci-check-mode | Compile first, then run scalafixAll --check on CI |
| scalafix-explicitresulttypes-rule | Add explicit result types with the ExplicitResultTypes rule |
| scalafix-removeunused-rule | Prune unused imports and locals with the RemoveUnused rule |
| scalafix-semantic-rules-require-semanticdb | Enable SemanticDB before running Scalafix's semantic rules |
Scalafmt [HIGH]
Commit a pinned Scalafmt config and enforce it in CI check mode, never write mode.
| Rule | Description |
|---|---|
| scalafmt-ci-check-mode | Run Scalafmt in check mode on CI, never in write mode |
| scalafmt-config-committed | Commit .scalafmt.conf with a pinned version key |
| scalafmt-import-sorting | Sort and group imports with the Imports rewrite rule |
| scalafmt-max-column-consistency | Pick one maxColumn value and keep the IDE reading the same config |
Wartremover [HIGH]
Ban unsafe patterns at compile time with Wartremover, scoped and baselined so it doesn't block legacy code.
| Rule | Description |
|---|---|
| wartremover-ban-null-var-any | Ban Wart.Null, Wart.Var, and Wart.Any at compile time |
| wartremover-ci-integration | Scope Wartremover to compile, not test, and let it fail via errors not warnings |
| wartremover-scalastyle-baseline-for-legacy-code | Baseline Wartremover and Scalastyle instead of enforcing everywhere on day one |
Scalastyle [LOW]
Gate line length and naming style with a committed scalastyle-config.xml.
| Rule | Description |
|---|---|
| scalastyle-line-length-and-style-checks | Gate line length and naming style with scalastyle-config.xml |
Quick Reference
Build
// build.sbt
lazy val commonSettings = Seq(
scalaVersion := "3.3.3",
organization := "com.example"
)
lazy val core = (project in file("core"))
.settings(commonSettings)
.settings(name := "myapp-core")
lazy val api = (project in file("api"))
.dependsOn(core)
.settings(commonSettings)
.settings(name := "myapp-api")
lazy val root = (project in file("."))
.aggregate(core, api)
.settings(publish / skip := true)
Compiler Flags
// build.sbt — fatal by default, with a deliberate, visible carve-out
scalacOptions ++= Seq(
"-Xfatal-warnings",
"-Wconf:cat=deprecation:s"
)
Scalafix
# CI
- run: sbt compile
- run: sbt "scalafixAll --check"
Scalafmt
# CI job — fails the build if anything would change
- run: sbt scalafmtCheckAll scalafmtSbtCheck
Wartremover
final class Cache(entries: Map[String, String]) {
def get(key: String): Option[String] =
entries.get(key)
}
Scalastyle
<!-- scalastyle-config.xml -->
<scalastyle>
<check level="error" class="org.scalastyle.file.FileLineLengthChecker" enabled="true">
<parameters><parameter name="maxLineLength">120</parameter></parameters>
</check>
<check level="error" class="org.scalastyle.scalariform.ClassNamesChecker" enabled="true">
<parameters><parameter name="regex">[A-Z][A-Za-z0-9]*</parameter></parameters>
</check>
</scalastyle>
See Also
- scala-coding-standards - General Scala coding standards and best practices
- scala-testing - Test-writing best practices for MUnit, ScalaTest, ScalaCheck, and mocking discipline