Skip to content

Commit 892bbb8

Browse files
jhl221123timtebeekgithub-actions[bot]Jenson3210
authored
Add ChangeTaskToTasksRegister recipe (#5492)
* Add ChangeTaskToTasksRegister recipe * Update ChangeTaskToTasksRegister recipe to support both groovy and kotlin * Update rewrite-gradle/src/test/java/org/openrewrite/gradle/ChangeTaskToTasksRegisterTest.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Update precondition and recipe list in YAML * Improve task identification for project and subproject scopes * Unify Groovy/Kotlin visitors into a single JavaVisitor * Reverted to previous approach and corrected its template usage to not use print. * Reverted to previous approach and corrected its template usage to not use print. * Improve recipe stability with hybrid template approach * Update rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeTaskToTasksRegister.java Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Reduce test memory consumption to see if this fixes something * Apply formatting * Add ChangeTaskToTasksRegister to best practices * Revert changes to gradle.yml * Restore reference to recipe * Show a limitation of the current implementation * Fix detected issue --------- Co-authored-by: Tim te Beek <timtebeek@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Jente Sondervorst <jentesondervorst@gmail.com> Co-authored-by: Tim te Beek <tim@moderne.io>
1 parent 58a608d commit 892bbb8

5 files changed

Lines changed: 725 additions & 1 deletion

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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.gradle;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.jspecify.annotations.Nullable;
21+
import org.openrewrite.*;
22+
import org.openrewrite.groovy.GroovyIsoVisitor;
23+
import org.openrewrite.groovy.GroovyTemplate;
24+
import org.openrewrite.groovy.tree.G;
25+
import org.openrewrite.internal.ListUtils;
26+
import org.openrewrite.java.tree.Expression;
27+
import org.openrewrite.java.tree.J;
28+
import org.openrewrite.java.tree.JavaType;
29+
import org.openrewrite.java.tree.TypeUtils;
30+
import org.openrewrite.kotlin.KotlinIsoVisitor;
31+
import org.openrewrite.kotlin.KotlinTemplate;
32+
import org.openrewrite.kotlin.tree.K;
33+
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
37+
@Value
38+
@EqualsAndHashCode(callSuper = false)
39+
public class ChangeTaskToTasksRegister extends Recipe {
40+
41+
@Override
42+
public String getDisplayName() {
43+
return "Change Gradle task eager creation to lazy registration";
44+
}
45+
46+
@Override
47+
public String getDescription() {
48+
return "Changes eager task creation `task exampleName(type: ExampleType)` to lazy registration `tasks.register(\"exampleName\", ExampleType)`. " +
49+
"Also supports Kotlin DSL: `task<ExampleType>(\"exampleName\")` to `tasks.register<ExampleType>(\"exampleName\")`.";
50+
}
51+
52+
@Override
53+
public TreeVisitor<?, ExecutionContext> getVisitor() {
54+
return Preconditions.check(new IsBuildGradle<>(), new TreeVisitor<Tree, ExecutionContext>() {
55+
56+
@Override
57+
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
58+
if (tree == null) {
59+
return null;
60+
}
61+
if (tree instanceof G.CompilationUnit) {
62+
return new GroovyVisitor().visit(tree, ctx);
63+
}
64+
if (tree instanceof K.CompilationUnit) {
65+
return new KotlinVisitor().visit(tree, ctx);
66+
}
67+
return tree;
68+
}
69+
}
70+
);
71+
}
72+
73+
private static class GroovyVisitor extends GroovyIsoVisitor<ExecutionContext> {
74+
75+
@Override
76+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
77+
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
78+
if (!isTaskDeclaration(m, getCursor())) {
79+
return m;
80+
}
81+
82+
List<Expression> args = m.getArguments();
83+
if (args.isEmpty() || !(args.get(0) instanceof J.MethodInvocation)) {
84+
return m;
85+
}
86+
87+
J.MethodInvocation inner = (J.MethodInvocation) args.get(0);
88+
List<Expression> innerArgs = inner.getArguments();
89+
if (innerArgs.isEmpty()) {
90+
return m;
91+
}
92+
93+
Expression taskType = null;
94+
J.Lambda taskLambda = null;
95+
for (Expression innerArg : innerArgs) {
96+
if (innerArg instanceof G.MapEntry) {
97+
G.MapEntry mapEntry = (G.MapEntry) innerArg;
98+
Expression key = mapEntry.getKey();
99+
String keyName = null;
100+
if (key instanceof G.Literal) {
101+
Object value = ((G.Literal) key).getValue();
102+
if (value instanceof String) {
103+
keyName = (String) value;
104+
}
105+
}
106+
107+
if ("type".equals(keyName)) {
108+
taskType = mapEntry.getValue();
109+
} else {
110+
// Unsupported map entry (e.g. dependsOn, group, overwrite);
111+
// cannot be translated to tasks.register() arguments
112+
return m;
113+
}
114+
} else if (innerArg instanceof J.Lambda) {
115+
taskLambda = (J.Lambda) innerArg;
116+
}
117+
}
118+
119+
StringBuilder template = new StringBuilder();
120+
List<Object> parameters = new ArrayList<>();
121+
Expression select = m.getSelect();
122+
if (select != null) {
123+
template.append("#{any()}.");
124+
parameters.add(select.withType(JavaType.Primitive.Void)); // Bypass the type check for `project.tasks` not found
125+
}
126+
template.append("tasks.register(\"#{}\")");
127+
parameters.add(inner.getSimpleName());
128+
129+
J.MethodInvocation taskRegistration = GroovyTemplate.apply(template.toString(), getCursor(), m.getCoordinates().replace(), parameters.toArray());
130+
List<Expression> appendArgs = new ArrayList<>();
131+
if (taskType != null) {
132+
appendArgs.add(taskType);
133+
}
134+
if (taskLambda != null) {
135+
appendArgs.add(taskLambda);
136+
}
137+
return taskRegistration.withArguments(ListUtils.concatAll(taskRegistration.getArguments(), appendArgs));
138+
}
139+
140+
private boolean isTaskDeclaration(J.MethodInvocation method, Cursor cursor) {
141+
if (!"task".equals(method.getSimpleName())) {
142+
return false;
143+
}
144+
Expression select = method.getSelect();
145+
if (select == null) {
146+
return true;
147+
}
148+
149+
if (select instanceof J.Identifier) {
150+
String selectName = ((J.Identifier) select).getSimpleName();
151+
if ("project".equals(selectName) || "it".equals(selectName)) {
152+
return true;
153+
}
154+
155+
J.Lambda enclosingLambda = cursor.firstEnclosing(J.Lambda.class);
156+
if (enclosingLambda != null) {
157+
for (J param : enclosingLambda.getParameters().getParameters()) {
158+
if (param instanceof J.VariableDeclarations) {
159+
J.VariableDeclarations varDecls = (J.VariableDeclarations) param;
160+
if (!varDecls.getVariables().isEmpty() &&
161+
varDecls.getVariables().get(0).getName().getSimpleName().equals(selectName)) {
162+
return true;
163+
}
164+
}
165+
}
166+
}
167+
}
168+
return false;
169+
}
170+
}
171+
172+
private static class KotlinVisitor extends KotlinIsoVisitor<ExecutionContext> {
173+
174+
@Override
175+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
176+
J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
177+
if (!isTaskDeclaration(m)) {
178+
return m;
179+
}
180+
181+
List<Expression> args = m.getArguments();
182+
if (args.isEmpty() || !(args.get(0) instanceof J.Literal)) {
183+
return m;
184+
}
185+
186+
J.Literal taskName = (J.Literal) args.get(0);
187+
if (taskName.getValue() == null || !TypeUtils.isString(taskName.getType())) {
188+
return m;
189+
}
190+
191+
J.Lambda taskLambda = null;
192+
if (args.size() == 2 && args.get(1) instanceof J.Lambda) {
193+
taskLambda = (J.Lambda) args.get(1);
194+
}
195+
196+
StringBuilder template = new StringBuilder();
197+
List<Object> parameters = new ArrayList<>();
198+
Expression select = m.getSelect();
199+
if (select != null) {
200+
template.append("#{any()}.");
201+
parameters.add(select);
202+
}
203+
template.append("tasks.register(\"#{}\")");
204+
parameters.add(taskName.getValue());
205+
206+
J.MethodInvocation taskRegistration = KotlinTemplate.apply(template.toString(), getCursor(), m.getCoordinates().replace(), parameters.toArray());
207+
208+
return m.withSelect(taskRegistration.getSelect())
209+
.withName(taskRegistration.getName())
210+
.withArguments(ListUtils.concat((taskRegistration).getArguments(), taskLambda));
211+
}
212+
213+
private boolean isTaskDeclaration(J.MethodInvocation method) {
214+
if (!"task".equals(method.getSimpleName())) {
215+
return false;
216+
}
217+
218+
Expression select = method.getSelect();
219+
if (select == null) {
220+
return true;
221+
}
222+
return select instanceof J.Identifier && "project".equals(((J.Identifier) select).getSimpleName());
223+
}
224+
}
225+
}

rewrite-gradle/src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,38 @@ examples:
873873
language: groovy
874874
---
875875
type: specs.openrewrite.org/v1beta/example
876+
recipeName: org.openrewrite.gradle.ChangeTaskToTasksRegister
877+
examples:
878+
- description: ''
879+
sources:
880+
- before: |
881+
task exampleName(type: Copy) {
882+
from 'src/main/resources'
883+
into 'build/generated-resources'
884+
}
885+
after: |
886+
tasks.register("exampleName", Copy) {
887+
from 'src/main/resources'
888+
into 'build/generated-resources'
889+
}
890+
path: build.gradle
891+
language: groovy
892+
- description: ''
893+
sources:
894+
- before: |
895+
task<Copy>("exampleName") {
896+
from("src")
897+
into("dest")
898+
}
899+
after: |
900+
tasks.register<Copy>("exampleName") {
901+
from("src")
902+
into("dest")
903+
}
904+
path: build.gradle.kts
905+
language: kotlin
906+
---
907+
type: specs.openrewrite.org/v1beta/example
876908
recipeName: org.openrewrite.gradle.gradle8.JacocoReportDeprecations
877909
examples:
878910
- description: '`JacocoReportDeprecationsTest#deprecationsInNormalSyntax`'

rewrite-gradle/src/main/resources/META-INF/rewrite/gradle.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ recipeList:
2222
- org.openrewrite.gradle.MigrateToGradle9
2323
- org.openrewrite.gradle.EnableGradleBuildCache
2424
- org.openrewrite.gradle.EnableGradleParallelExecution
25+
- org.openrewrite.gradle.ChangeTaskToTasksRegister
2526
---
2627
type: specs.openrewrite.org/v1beta/recipe
2728
name: org.openrewrite.gradle.EnableGradleBuildCache

0 commit comments

Comments
 (0)