Skip to content

Commit aee6375

Browse files
committed
feat(scala): Add Scala 3 language support module
Add rewrite-scala module with Scala 3 (Dotty) compiler integration for parsing Scala source code into OpenRewrite's Lossless Semantic Tree (LST). Key features: - ScalaTreeVisitor: Core AST converter from Dotty untyped trees to LST - ScalaPrinter: LST-to-source-code printer with Scala-specific syntax - S.java: Scala-specific AST types (MatchExpression, CaseClause, TuplePattern, InterpolatedString, BlockExpression, Wildcard, TypeAlias, FunctionType, InfixType, Extension, ForComprehension, etc.) - Marker system for Scala idioms: BlockArgument, InfixNotation, FunctionApplication, LambdaParameter, ScalaForLoop, OmitBraces, ColonToken (fewer braces), and more - Support for selective/aliased/wildcard/given imports - Comprehensive test suite covering expressions, statements, classes, traits, objects, pattern matching, control flow, lambdas, generics, string interpolation, and Scala 3 features
1 parent ef2009a commit aee6375

80 files changed

Lines changed: 20587 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Scala.md

Lines changed: 644 additions & 0 deletions
Large diffs are not rendered by default.

rewrite-scala/CLAUDE.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# rewrite-scala
2+
3+
Scala language support for OpenRewrite, following the same pattern as rewrite-kotlin and rewrite-groovy.
4+
5+
## Active Development Plans
6+
7+
- **Scala Language Support**: See [Scala.md](../Scala.md) for the implementation plan and progress tracking.
8+
- **Language Support Documentation**: As we implement Scala support, we're documenting the process in [Contributing Additional Language Support](../rewrite-docs/docs/authoring-recipes/contributing-language-support.md). This guide should be continuously updated with lessons learned during implementation.
9+
10+
## CRITICAL PRINCIPLES - NEVER VIOLATE THESE
11+
12+
### Never Regress from Rich Types to J.Unknown
13+
**ABSOLUTE RULE**: Once a syntax element has been mapped to a rich type (J.* or S.*), NEVER revert it back to J.Unknown. This is a fundamental architectural principle. J.Unknown should only be used for:
14+
1. Syntax we haven't implemented yet
15+
2. Temporary placeholders during initial development
16+
3. Truly unparseable or corrupted code
17+
18+
If you find yourself wanting to use J.Unknown for something already mapped, you're doing it wrong. Instead:
19+
- Create a new S.* type if needed
20+
- Use markers to preserve special behavior
21+
- Extend existing J.* types with Scala-specific markers
22+
- Find a way to map it to existing rich types
23+
24+
Going back to J.Unknown breaks type safety, loses semantic information, and makes the AST less useful for recipes.
25+
26+
## Architecture
27+
28+
- `S` interface extends `J` (Java's LST interface)
29+
- Reuses common JVM constructs from the J model
30+
- Adds Scala-specific constructs (pattern matching, traits, implicits, etc.) to the S interface
31+
- Uses Scala 3 (Dotty) compiler for parsing
32+
- LST model classes are implemented in Java (not Scala), following the K.java / G.java pattern
33+
34+
## Key Files
35+
36+
- `src/main/java/org/openrewrite/scala/tree/S.java` - Scala-specific AST types
37+
- `src/main/java/org/openrewrite/scala/ScalaParserVisitor.java` - Bridges Scala compiler AST to LST
38+
- `src/main/java/org/openrewrite/scala/ScalaPrinter.java` - LST to source code
39+
- `src/main/java/org/openrewrite/scala/ScalaVisitor.java` - Base visitor
40+
- `src/main/scala/org/openrewrite/scala/ScalaTreeVisitor.scala` - Core tree traversal
41+
42+
## Build Commands
43+
44+
```bash
45+
./gradlew :rewrite-scala:assemble # Compile only
46+
./gradlew :rewrite-scala:test # Run tests
47+
```

rewrite-scala/build.gradle.kts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
plugins {
2+
id("org.openrewrite.build.language-library")
3+
scala
4+
}
5+
6+
dependencies {
7+
api(project(":rewrite-java"))
8+
9+
// Scala 3 compiler (dotty) and library
10+
implementation("org.scala-lang:scala3-compiler_3:latest.release")
11+
implementation("org.scala-lang:scala3-library_3:latest.release")
12+
13+
compileOnly(project(":rewrite-test"))
14+
compileOnly("org.slf4j:slf4j-api:1.7.+")
15+
16+
api("io.micrometer:micrometer-core:1.9.+")
17+
18+
api("org.jetbrains:annotations:latest.release")
19+
20+
api("com.fasterxml.jackson.core:jackson-annotations")
21+
22+
testImplementation(project(":rewrite-test"))
23+
testImplementation(project(":rewrite-java-test"))
24+
testImplementation("org.assertj:assertj-core:latest.release")
25+
testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release")
26+
testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release")
27+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release")
28+
}
29+
30+
// Configure Scala source sets and compilation order
31+
sourceSets {
32+
main {
33+
scala {
34+
srcDirs("src/main/scala")
35+
}
36+
}
37+
}
38+
39+
// Configure mixed Java/Scala compilation
40+
// Scala needs to see Java classes from the same module
41+
tasks.named<ScalaCompile>("compileScala") {
42+
// Include Java source files in Scala compilation
43+
source(sourceSets.main.get().java)
44+
// Scala compiler will compile both Java and Scala files together
45+
classpath = sourceSets.main.get().compileClasspath
46+
}
47+
48+
// Ensure Java compilation uses output from Scala compilation
49+
// Since Scala already compiled Java files, we just need to ensure the classpath is correct
50+
tasks.named<JavaCompile>("compileJava") {
51+
dependsOn("compileScala")
52+
// Exclude Java files from Java compilation since Scala already compiled them
53+
exclude("**/*.java")
54+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.scala;
17+
18+
import org.intellij.lang.annotations.Language;
19+
import org.jspecify.annotations.Nullable;
20+
import org.openrewrite.SourceFile;
21+
import org.openrewrite.java.JavaParser;
22+
import org.openrewrite.scala.tree.S;
23+
import org.openrewrite.test.SourceSpec;
24+
import org.openrewrite.test.SourceSpecs;
25+
26+
import java.util.function.Consumer;
27+
28+
import static org.openrewrite.java.Assertions.sourceSet;
29+
import static org.openrewrite.test.SourceSpecs.dir;
30+
31+
public class Assertions {
32+
33+
private Assertions() {
34+
}
35+
36+
private static ScalaParser.Builder scalaParser = ScalaParser.builder()
37+
.classpath(JavaParser.runtimeClasspath())
38+
.logCompilationWarningsAndErrors(true);
39+
40+
public static SourceSpecs scala(@Language("scala") @Nullable String before) {
41+
return scala(before, s -> {
42+
});
43+
}
44+
45+
public static SourceSpecs scala(@Language("scala") @Nullable String before, Consumer<SourceSpec<S.CompilationUnit>> spec) {
46+
SourceSpec<S.CompilationUnit> scala = new SourceSpec<>(S.CompilationUnit.class, null, scalaParser, before, null);
47+
spec.accept(scala);
48+
return scala;
49+
}
50+
51+
public static SourceSpecs scala(@Language("scala") @Nullable String before, @Language("scala") @Nullable String after) {
52+
return scala(before, after, s -> {
53+
});
54+
}
55+
56+
public static SourceSpecs scala(@Language("scala") @Nullable String before, @Language("scala") @Nullable String after,
57+
Consumer<SourceSpec<S.CompilationUnit>> spec) {
58+
SourceSpec<S.CompilationUnit> scala = new SourceSpec<>(S.CompilationUnit.class, null, scalaParser, before, s -> after);
59+
spec.accept(scala);
60+
return scala;
61+
}
62+
63+
public static SourceSpecs srcMainScala(Consumer<SourceSpec<SourceFile>> spec, SourceSpecs... scalaSources) {
64+
return dir("src/main/scala", spec, scalaSources);
65+
}
66+
67+
public static SourceSpecs srcMainScala(SourceSpecs... scalaSources) {
68+
return srcMainScala(spec -> sourceSet(spec, "main"), scalaSources);
69+
}
70+
71+
public static SourceSpecs srcTestScala(Consumer<SourceSpec<SourceFile>> spec, SourceSpecs... scalaSources) {
72+
return dir("src/test/scala", spec, scalaSources);
73+
}
74+
75+
public static SourceSpecs srcTestScala(SourceSpecs... scalaSources) {
76+
return srcTestScala(spec -> sourceSet(spec, "test"), scalaSources);
77+
}
78+
}

0 commit comments

Comments
 (0)