Skip to content

Commit 2fda680

Browse files
authored
Add Kotlin script template support for Gradle KTS (#6792)
* Add Kotlin script template support for Gradle KTS parsing Leverage K2 FIR compiler extensions to configure implicit receivers and default imports for build.gradle.kts and settings.gradle.kts files. This improves type information for Gradle DSL methods, enabling better recipe matching and method resolution. Implements FirScriptConfiguratorExtension and FirScriptResolutionConfigurationExtension to provide Project/Settings as implicit receivers and Gradle API packages as default imports. * Unify Gradle recipe visitors for Groovy and Kotlin DSL Now that Kotlin script templates provide type information for .gradle.kts files, merge separate GroovyVisitor/KotlinVisitor pairs into single JavaVisitor implementations where possible. - UpdateJavaCompatibility: replace dual GroovyIsoVisitor + KotlinIsoVisitor with a single JavaVisitor that handles both DSLs - UpgradeTransitiveDependencyVersion: replace inline GroovyIsoVisitor and KotlinIsoVisitor literal visitors with shared ChangeStringLiteral, and simplify the constraints precondition check * Fix FindRepository matching for KTS with type information Replace MethodMatcher-based pluginManagement/buildscript detection with simple name matching, since the synthetic class names (RewriteSettings, RewriteGradleProject) don't match the real types resolved by the K2 compiler (org.gradle.api.initialization.Settings, org.gradle.api.Project). * Add FIR-to-IR conversion phase to KotlinParser (#6793) After FIR analysis, run JvmFir2IrPipelinePhase to populate the irFile field on KotlinSource, enabling future use of IR-based type mapping for improved Kotlin parsing. * Unify more Gradle recipe visitors for Groovy and Kotlin DSL Convert ChangeDependencyGroupId and ChangeDependencyArtifactId from GroovyIsoVisitor to JavaIsoVisitor so they work with both Groovy and Kotlin DSL build files. * Use real Gradle API types in MethodMatcher patterns for KTS support Update MethodMatcher patterns from synthetic Groovy template types (RewriteGradleProject, RewriteSettings, PluginSpec, Plugin) to real Gradle API types (org.gradle.api.Project, Settings, PluginDependenciesSpec, PluginDependencySpec) with matchOverrides=true, enabling matching for both Groovy DSL and Kotlin DSL scripts. * Increase test heap to 2g for rewrite-gradle The K2 compiler used for KTS parsing loads many JDK modules and consumes significant memory. The default test worker heap is insufficient, causing OutOfMemoryError on CI. * Use regular imports in Plugin.groovy * Restore `isKotlin` conditions in RemovePluginVisitor due to missing types * Verify `ChangeDependencyArtifactId` works with .kts * Verify `ChangeDependencyGroupId` works with .kts * Verify `DependencyInsight` works with .kts * Verify `DependencyConstraintToRule` works with .kts * Merge Groovy/Kotlin visitors into single JavaIsoVisitor in `UpdateJavaCompatibility`
1 parent d4392fc commit 2fda680

22 files changed

Lines changed: 550 additions & 171 deletions

rewrite-gradle/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ java {
8989
tasks.withType<Test>().configureEach {
9090
dependsOn(pluginLocalTestClasspath)
9191
systemProperty("org.openrewrite.gradle.local.use-embedded-classpath", pluginLocalTestClasspath.files.find { it.name == "test-manifest.txt" }!!.path)
92+
maxHeapSize = "2g"
9293
}
9394

9495
// This seems to be the only way to get the groovy compiler to emit java-8 compatible bytecode

rewrite-gradle/src/main/groovy/Plugin.groovy

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
interface PluginSpec {
16+
import org.gradle.plugin.use.PluginDependenciesSpec
17+
import org.gradle.plugin.use.PluginDependencySpec
18+
19+
interface PluginSpec extends PluginDependenciesSpec {
1720
Plugin id(String i)
1821
}
1922

20-
interface Plugin {
23+
interface Plugin extends PluginDependencySpec {
2124
Plugin version(String v)
2225
Plugin apply(boolean a)
2326
}

rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyArtifactId.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
2323
import org.openrewrite.gradle.marker.GradleProject;
2424
import org.openrewrite.gradle.trait.GradleMultiDependency;
25-
import org.openrewrite.groovy.GroovyIsoVisitor;
26-
import org.openrewrite.groovy.tree.G;
2725
import org.openrewrite.internal.ListUtils;
2826
import org.openrewrite.internal.StringUtils;
27+
import org.openrewrite.java.JavaIsoVisitor;
2928
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.java.tree.JavaSourceFile;
3030
import org.openrewrite.semver.DependencyMatcher;
3131

3232
import java.util.HashMap;
@@ -76,26 +76,28 @@ public Validated<Object> validate() {
7676

7777
@Override
7878
public TreeVisitor<?, ExecutionContext> getVisitor() {
79-
return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor<ExecutionContext>() {
79+
return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor<ExecutionContext>() {
8080
final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue());
8181

8282
@SuppressWarnings("NotNullFieldNotInitialized")
8383
GradleProject gradleProject;
8484

8585
@Override
86-
public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) {
87-
Optional<GradleProject> maybeGp = cu.getMarkers().findFirst(GradleProject.class);
88-
if (!maybeGp.isPresent()) {
89-
return cu;
90-
}
91-
92-
gradleProject = maybeGp.get();
93-
94-
G.CompilationUnit g = super.visitCompilationUnit(cu, ctx);
95-
if (g != cu) {
96-
g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject)));
86+
public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) {
87+
if (tree instanceof JavaSourceFile) {
88+
JavaSourceFile sf = (JavaSourceFile) tree;
89+
Optional<GradleProject> maybeGp = sf.getMarkers().findFirst(GradleProject.class);
90+
if (maybeGp.isPresent()) {
91+
gradleProject = maybeGp.get();
92+
J result = super.visit(tree, ctx);
93+
if (result != tree && result instanceof JavaSourceFile) {
94+
JavaSourceFile updated = (JavaSourceFile) result;
95+
return updated.withMarkers(updated.getMarkers().setByType(updateGradleModel(gradleProject)));
96+
}
97+
}
98+
return sf;
9799
}
98-
return g;
100+
return super.visit(tree, ctx);
99101
}
100102

101103
@Override

rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependencyGroupId.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@
2222
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
2323
import org.openrewrite.gradle.marker.GradleProject;
2424
import org.openrewrite.gradle.trait.GradleMultiDependency;
25-
import org.openrewrite.groovy.GroovyIsoVisitor;
26-
import org.openrewrite.groovy.tree.G;
2725
import org.openrewrite.internal.ListUtils;
2826
import org.openrewrite.internal.StringUtils;
27+
import org.openrewrite.java.JavaIsoVisitor;
2928
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.java.tree.JavaSourceFile;
3030
import org.openrewrite.semver.DependencyMatcher;
3131

3232
import java.util.HashMap;
@@ -76,26 +76,28 @@ public Validated<Object> validate() {
7676

7777
@Override
7878
public TreeVisitor<?, ExecutionContext> getVisitor() {
79-
return Preconditions.check(new IsBuildGradle<>(), new GroovyIsoVisitor<ExecutionContext>() {
79+
return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor<ExecutionContext>() {
8080
final DependencyMatcher depMatcher = requireNonNull(DependencyMatcher.build(groupId + ":" + artifactId).getValue());
8181

8282
@SuppressWarnings("NotNullFieldNotInitialized")
8383
GradleProject gradleProject;
8484

8585
@Override
86-
public G.CompilationUnit visitCompilationUnit(G.CompilationUnit cu, ExecutionContext ctx) {
87-
Optional<GradleProject> maybeGp = cu.getMarkers().findFirst(GradleProject.class);
88-
if (!maybeGp.isPresent()) {
89-
return cu;
90-
}
91-
92-
gradleProject = maybeGp.get();
93-
94-
G.CompilationUnit g = super.visitCompilationUnit(cu, ctx);
95-
if (g != cu) {
96-
g = g.withMarkers(g.getMarkers().setByType(updateGradleModel(gradleProject)));
86+
public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) {
87+
if (tree instanceof JavaSourceFile) {
88+
JavaSourceFile sf = (JavaSourceFile) tree;
89+
Optional<GradleProject> maybeGp = sf.getMarkers().findFirst(GradleProject.class);
90+
if (maybeGp.isPresent()) {
91+
gradleProject = maybeGp.get();
92+
J result = super.visit(tree, ctx);
93+
if (result != tree && result instanceof JavaSourceFile) {
94+
JavaSourceFile updated = (JavaSourceFile) result;
95+
return updated.withMarkers(updated.getMarkers().setByType(updateGradleModel(gradleProject)));
96+
}
97+
}
98+
return sf;
9799
}
98-
return g;
100+
return super.visit(tree, ctx);
99101
}
100102

101103
@Override

rewrite-gradle/src/main/java/org/openrewrite/gradle/DependencyConstraintToRule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
@EqualsAndHashCode(callSuper = false)
4646
public class DependencyConstraintToRule extends Recipe {
4747

48-
private static final MethodMatcher DEPENDENCIES_DSL_MATCHER = new MethodMatcher("RewriteGradleProject dependencies(..)");
48+
private static final MethodMatcher DEPENDENCIES_DSL_MATCHER = new MethodMatcher("org.gradle.api.Project dependencies(..)", true);
4949
private static final String CONSTRAINT_MATCHER = "org.gradle.api.artifacts.dsl.DependencyHandler *(..)";
5050

5151
String displayName = "Dependency constraint to resolution rule";

rewrite-gradle/src/main/java/org/openrewrite/gradle/GradleParser.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
6666
}
6767
kotlinBuildParser = KotlinParser.builder(base.kotlinParser)
6868
.classpath(buildscriptClasspath)
69+
.isKotlinScript(true)
70+
.scriptImplicitReceivers("org.gradle.api.Project")
71+
.scriptDefaultImports(DefaultImportsCustomizer.DEFAULT_IMPORTS)
6972
.build();
7073
}
7174
if (groovySettingsParser == null) {
@@ -88,6 +91,9 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
8891
}
8992
kotlinSettingsParser = KotlinParser.builder(base.kotlinParser)
9093
.classpath(settingsClasspath)
94+
.isKotlinScript(true)
95+
.scriptImplicitReceivers("org.gradle.api.initialization.Settings")
96+
.scriptDefaultImports(DefaultImportsCustomizer.DEFAULT_IMPORTS)
9197
.build();
9298
}
9399

0 commit comments

Comments
 (0)