Scalafix is a refactoring and linting tool for Scala. It enables syntactic and semantic source code transformations through a rule-based system. The project targets multiple Scala versions (2.12, 2.13, 3.3-3.8) and is built using a complex sbt-projectmatrix setup.
Core Architecture:
scalafix-interfaces: Java API for JVM integration (cross-platform, no Scala dependency)scalafix-core: Data structures for AST rewriting and linting (Scala 2.12/2.13)scalafix-rules: Built-in rules likeRemoveUnused,ProcedureSyntax(cross-compiled for all Scala versions)scalafix-reflect: Runtime rule compilation (Scala 2 only)scalafix-cli: Command-line interface (cross-compiled for all Scala versions)scalafix-testkit: Testing framework for rules using input/output file pairs
The build uses sbt-projectmatrix to generate multiple sub-projects per module, cross-compiled against different Scala versions. This is NOT standard Scala cross-building.
TargetAxis System: Custom mechanism in project/TargetAxis.scala that creates test matrix combinations:
- Each test project is built with a specific Scala version (the "target")
- Test frameworks compiled with another Scala version can test against that target
- Some targets include
-Xsource:3flag (tagged withXsource3Axis) - Example:
expect3_3_6Target3_3_6tests rules compiled with 3.3.6 against input also compiled with 3.3.6
Key build concepts:
projectMatrix: Defines modules that cross-compile.jvmPlatform(scalaVersions): Creates one sub-project per Scala version.jvmPlatformTargets(targets): Creates sub-projects for test targets.jvmPlatformAgainstTargets(tuples): Creates test projects that run with one Scala version against target compiled with another- Use
resolve(matrix, key)to lookup and extract settings from the matching sub-project
# Unit tests for latest Scala 3.3.6 version
sbt "unit3_3_6 / test"
# Integration tests (contains many suites - use testOnly to narrow)
sbt "integration3_3_6 / testOnly -- -z ProcedureSyntax"
# Test built-in rules using scalafix-testkit
sbt "expect3_3_6Target3_3_6 / test"
# Windows-compatible tests only
sbt "unit3_3_6 / testWindows"Pattern: Test project names combine the Scala version used to compile the test framework with the target being tested: unit{CompilerVersion} or expect{CompilerVersion}Target{TargetVersion}
# First, publish local artifacts for all Scala versions
sbt "dogfoodScalafixInterfaces; scalafixAll"The dogfoodScalafixInterfaces command:
- Runs
publishLocalTransitiveon all CLI versions - Reloads sbt plugins
- Overrides
scalafix-interfacesversion in meta-build - Returns to main build
# Format all files
./bin/scalafmt
# Format only changed files
./bin/scalafmt --diff# Generate docs with mdoc and start Docusaurus server
sbt ci-docs
cd website && yarn start- SyntacticRule: Works with AST only, no semantic information (fast)
- Example:
ProcedureSyntax- replacesdef foo { }withdef foo: Unit = { }
- Example:
- SemanticRule: Requires SemanticDB (compiler symbols, types)
- Example:
RemoveUnused- removes imports/terms flagged by-Wunused - Requires
-Wunused,-Ywarn-unused, or-Wallcompiler flag
- Example:
Rules extend scalafix.v1.Rule and must:
- Define in
scalafix-rules/src/main/scala/scalafix/internal/rule/ - Register in
src/main/resources/META-INF/services/scalafix.v1.Rule - Return
Patchoperations (not string diffs) - Use
.atomicon patches for suppression support
Key APIs:
Patch.addLeft(tree, string)/Patch.addRight(tree, string): Insert textPatch.removeToken(token): Delete tokenPatch.replaceTree(tree, string): Replace tree nodetree.symbol: Get symbol from tree (requiresSemanticDocument)SymbolMatcher: Pattern match symbols likeval HasOption = SymbolMatcher.normalized("scala/Option#")tree.collect { case ... }: Traverse AST
Test structure in scalafix-tests/:
input/: Source files to transform (with special comments for assertions)output/: Expected output after rewriteexpect/: Test suite that compares input→output transformation
Special comment syntax in input files:
/* rules = MyRule */ // Configure which rules to run
import Unused // assert: UnusedImport (linter assertion)- Use
CrossVersion.for3Use2_13for Scala 3 depending on Scala 2.13 artifacts - Scala 3 rules depend on Scala 2.13 compiled
scalafix-core(no native Scala 3 core yet) - Use
semanticdbScalacCore.cross(CrossVersion.full)for compiler plugins
rulesdepends oncore(rewriting primitives)clidepends onrules+reflect(to load external rules)testkitdepends oncli(full execution environment)reflectonly exists for Scala 2 (no Scala 3 version)
- Use
sbt versionPolicyCheckbefore releasing scalafix.internal._packages have no compatibility guarantees- Public API in
scalafix.v1follows Early SemVer
.scalafix.conf: Rules applied when dogfooding (uses HOCON format)- Rules configured via
withConfiguration()method - Test files use
/* rules = ... */comments for configuration
- Wrong test scope: Use
unit3_3_6 / testnottest(ambiguous without projectmatrix context) - Missing BuildInfo: IntelliJ debugger requires running
sbt "unit3_3_6 / test"once to generate BuildInfo - Scala 3 reflect: No
reflect3module - Scala 3 rules can't dynamically compile - Dependency resolution: When bumping Scala versions, update
versionPolicyIgnoredinScalafixBuild.scala - Test failures on Windows: Use
testWindowstask, nottest
project/TargetAxis.scala: Understand test matrix cross-buildingproject/ScalafixBuild.scala: Custom sbt settings and commandsproject/Dependencies.scala: Scala version constantsscalafix-core/src/main/scala/scalafix/v1/Rule.scala: Rule base classesscalafix-testkit/src/main/scala/scalafix/testkit/SemanticRuleSuite.scala: Test frameworkdocs/developers/tutorial.md: Step-by-step rule creation guide