Skip to content

Commit f1dd96f

Browse files
shanman190timtebeekjevanlingen
authored
Add GradlePlugin trait (#5574)
* Add GradlePlugin trait * Add settings script and alias support * Polish * Apply suggestions from code review * Remove factory method as agreed upon * Polish snippets to ensure that all are valid Gradle * Add implementation dependency to rewrite-toml * Polish --------- Co-authored-by: Tim te Beek <tim@moderne.io> Co-authored-by: Jacob van Lingen <jacob.van.lingen@moderne.io>
1 parent 45c4389 commit f1dd96f

4 files changed

Lines changed: 1114 additions & 0 deletions

File tree

rewrite-gradle/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ dependencies {
5252
api("org.jetbrains:annotations:latest.release")
5353
compileOnly(project(":rewrite-test"))
5454
implementation(project(":rewrite-properties"))
55+
implementation(project(":rewrite-toml"))
5556

5657
compileOnly("org.codehaus.groovy:groovy:latest.release")
5758
compileOnly(gradleApi())
@@ -63,6 +64,7 @@ dependencies {
6364
exclude("ch.qos.logback", "logback-classic")
6465
exclude("org.slf4j", "slf4j-nop")
6566
}
67+
testImplementation(project(":rewrite-toml"))
6668

6769
testImplementation("org.openrewrite.gradle.tooling:model:$latest")
6870

rewrite-gradle/src/main/java/org/openrewrite/gradle/trait/GradleDependency.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
import lombok.Value;
2020
import org.jspecify.annotations.Nullable;
2121
import org.openrewrite.Cursor;
22+
import org.openrewrite.Tree;
23+
import org.openrewrite.TreeVisitor;
2224
import org.openrewrite.gradle.internal.DependencyStringNotationConverter;
2325
import org.openrewrite.gradle.marker.GradleDependencyConfiguration;
2426
import org.openrewrite.gradle.marker.GradleProject;
2527
import org.openrewrite.groovy.tree.G;
2628
import org.openrewrite.internal.StringUtils;
29+
import org.openrewrite.java.JavaVisitor;
2730
import org.openrewrite.java.MethodMatcher;
2831
import org.openrewrite.java.tree.Expression;
2932
import org.openrewrite.java.tree.J;
@@ -33,6 +36,7 @@
3336
import org.openrewrite.maven.tree.ResolvedDependency;
3437
import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion;
3538
import org.openrewrite.trait.Trait;
39+
import org.openrewrite.trait.VisitFunction2;
3640

3741
import java.util.Arrays;
3842
import java.util.List;
@@ -74,6 +78,19 @@ public Matcher artifactId(@Nullable String artifactId) {
7478
return this;
7579
}
7680

81+
@Override
82+
public <P> TreeVisitor<? extends Tree, P> asVisitor(VisitFunction2<GradleDependency, P> visitor) {
83+
return new JavaVisitor<P>() {
84+
@Override
85+
public J visitMethodInvocation(J.MethodInvocation method, P p) {
86+
GradleDependency dependency = test(getCursor());
87+
return dependency != null ?
88+
(J) visitor.visit(dependency, p) :
89+
super.visitMethodInvocation(method, p);
90+
}
91+
};
92+
}
93+
7794
@Override
7895
protected @Nullable GradleDependency test(Cursor cursor) {
7996
Object object = cursor.getValue();
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
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.trait;
17+
18+
import lombok.Value;
19+
import org.jspecify.annotations.Nullable;
20+
import org.openrewrite.Cursor;
21+
import org.openrewrite.Tree;
22+
import org.openrewrite.TreeVisitor;
23+
import org.openrewrite.gradle.marker.GradleProject;
24+
import org.openrewrite.groovy.tree.G;
25+
import org.openrewrite.internal.StringUtils;
26+
import org.openrewrite.java.JavaVisitor;
27+
import org.openrewrite.java.MethodMatcher;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.JavaSourceFile;
31+
import org.openrewrite.trait.Trait;
32+
import org.openrewrite.trait.VisitFunction2;
33+
34+
import static org.openrewrite.internal.StringUtils.matchesGlob;
35+
36+
@Value
37+
public class GradlePlugin implements Trait<J> {
38+
Cursor cursor;
39+
40+
@Nullable
41+
String pluginId;
42+
43+
@Nullable
44+
String pluginClass;
45+
46+
@Nullable
47+
String version;
48+
49+
boolean applied;
50+
51+
public static class Matcher extends GradleTraitMatcher<GradlePlugin> {
52+
private static final MethodMatcher ALIAS_DSL_MATCHER = new MethodMatcher("* alias(..)", false);
53+
private static final MethodMatcher APPLY_DSL_MATCHER = new MethodMatcher("* apply(..)", false);
54+
private static final MethodMatcher PLUGIN_ID_DSL_MATCHER = new MethodMatcher("* id(..)", false);
55+
private static final MethodMatcher KOTLIN_PLUGIN_DSL_MATCHER = new MethodMatcher("* kotlin(..)", false);
56+
private static final MethodMatcher PLUGIN_VERSION_DSL_MATCHER = new MethodMatcher("* version(..)", false);
57+
private static final String[] CORE_PLUGIN_NAMES = new String[]{
58+
"java", "java-base", "java-library", "java-platform", "groovy", "scala", "antlr", "jvm-test-suite", "test-report-aggregation",
59+
"application", "war", "ear", "maven-publish", "ivy-publish", "distribution", "java-library-distribution",
60+
"checkstyle", "pmd", "jacoco", "jacoco-report-aggregation", "codenarc", "eclipse", "eclipse-wtp", "idea",
61+
"visual-studio", "xcode", "base", "signing", "java-gradle-plugin", "project-report"};
62+
63+
@Nullable
64+
protected String pluginIdPattern;
65+
66+
@Nullable
67+
protected String pluginClass;
68+
69+
protected boolean acceptTransitive;
70+
71+
public GradlePlugin.Matcher pluginIdPattern(@Nullable String pluginIdPattern) {
72+
this.pluginIdPattern = pluginIdPattern;
73+
return this;
74+
}
75+
76+
public GradlePlugin.Matcher pluginClass(@Nullable String pluginClass) {
77+
this.pluginClass = pluginClass;
78+
return this;
79+
}
80+
81+
public GradlePlugin.Matcher acceptTransitive(boolean acceptTransitive) {
82+
this.acceptTransitive = acceptTransitive;
83+
return this;
84+
}
85+
86+
@Override
87+
public <P> TreeVisitor<? extends Tree, P> asVisitor(VisitFunction2<GradlePlugin, P> visitor) {
88+
return new JavaVisitor<P>() {
89+
@Override
90+
public @Nullable J visit(@Nullable Tree tree, P p) {
91+
J j = super.visit(tree, p);
92+
if (j instanceof JavaSourceFile) {
93+
GradlePlugin plugin = test(new Cursor(getCursor(), j));
94+
return plugin != null ?
95+
(J) visitor.visit(plugin, p) :
96+
j;
97+
}
98+
return j;
99+
}
100+
101+
@Override
102+
public J visitIdentifier(J.Identifier ident, P p) {
103+
GradlePlugin plugin = test(getCursor());
104+
return plugin != null ?
105+
(J) visitor.visit(plugin, p) :
106+
super.visitIdentifier(ident, p);
107+
}
108+
109+
@Override
110+
public J visitMethodInvocation(J.MethodInvocation method, P p) {
111+
GradlePlugin plugin = test(getCursor());
112+
return plugin != null ?
113+
(J) visitor.visit(plugin, p) :
114+
super.visitMethodInvocation(method, p);
115+
}
116+
};
117+
}
118+
119+
@Override
120+
protected @Nullable GradlePlugin test(Cursor cursor) {
121+
Object object = cursor.getValue();
122+
if (acceptTransitive && object instanceof JavaSourceFile) {
123+
GradleProject gp = getGradleProject(cursor);
124+
if (gp == null) {
125+
return null;
126+
}
127+
128+
return gp.getPlugins()
129+
.stream()
130+
.map(pluginDescriptor -> maybeGradlePlugin(cursor, pluginDescriptor.getId(), pluginDescriptor.getFullyQualifiedClassName(), null, true))
131+
.findFirst()
132+
.orElse(null);
133+
} else if (object instanceof J.MethodInvocation) {
134+
J.MethodInvocation m = (J.MethodInvocation) object;
135+
136+
if (withinPlugins(cursor)) {
137+
if (ALIAS_DSL_MATCHER.matches(m, true)) {
138+
if (!(m.getArguments().get(0) instanceof J.FieldAccess)) {
139+
return null;
140+
}
141+
142+
return maybeGradlePlugin(cursor, null, null, null, true);
143+
} else if (APPLY_DSL_MATCHER.matches(m, true)) {
144+
if (!(m.getArguments().get(0) instanceof J.Literal) || !(m.getSelect() instanceof J.MethodInvocation)) {
145+
return null;
146+
}
147+
148+
J.MethodInvocation versionSelect = (J.MethodInvocation) m.getSelect();
149+
if (!PLUGIN_VERSION_DSL_MATCHER.matches(versionSelect, true) ||
150+
!(versionSelect.getArguments().get(0) instanceof J.Literal) ||
151+
!(versionSelect.getSelect() instanceof J.MethodInvocation)) {
152+
return null;
153+
}
154+
155+
J.MethodInvocation idSelect = (J.MethodInvocation) versionSelect.getSelect();
156+
if (!(PLUGIN_ID_DSL_MATCHER.matches(idSelect, true) || KOTLIN_PLUGIN_DSL_MATCHER.matches(idSelect, true)) ||
157+
!(idSelect.getArguments().get(0) instanceof J.Literal)) {
158+
return null;
159+
}
160+
161+
J.Literal idLiteral = (J.Literal) idSelect.getArguments().get(0);
162+
J.Literal versionLiteral = (J.Literal) versionSelect.getArguments().get(0);
163+
J.Literal applyLiteral = (J.Literal) m.getArguments().get(0);
164+
String pluginId = "kotlin".equals(idSelect.getSimpleName()) ? "org.jetbrains.kotlin." + idLiteral.getValue() : (String) idLiteral.getValue();
165+
String version = (String) versionLiteral.getValue();
166+
boolean applied = Boolean.TRUE.equals(applyLiteral.getValue());
167+
return maybeGradlePlugin(cursor, pluginId, null, version, applied);
168+
} else if (PLUGIN_VERSION_DSL_MATCHER.matches(m, true)) {
169+
String version = null;
170+
if (m.getArguments().get(0) instanceof J.Literal) {
171+
J.Literal versionLiteral = (J.Literal) m.getArguments().get(0);
172+
version = (String) versionLiteral.getValue();
173+
}
174+
175+
if (!(m.getSelect() instanceof J.MethodInvocation &&
176+
(PLUGIN_ID_DSL_MATCHER.matches((J.MethodInvocation) m.getSelect(), true) || KOTLIN_PLUGIN_DSL_MATCHER.matches((J.MethodInvocation) m.getSelect(), true)))) {
177+
return null;
178+
}
179+
180+
J.MethodInvocation select = (J.MethodInvocation) m.getSelect();
181+
if (!(select.getArguments().get(0) instanceof J.Literal)) {
182+
return null;
183+
}
184+
185+
J.Literal idLiteral = (J.Literal) select.getArguments().get(0);
186+
String pluginId = "kotlin".equals(select.getSimpleName()) ? "org.jetbrains.kotlin." + idLiteral.getValue() : (String) idLiteral.getValue();
187+
return maybeGradlePlugin(cursor, pluginId, null, version, !withinBlock(cursor, "pluginManagement"));
188+
} else if (PLUGIN_ID_DSL_MATCHER.matches(m, true) || KOTLIN_PLUGIN_DSL_MATCHER.matches(m, true)) {
189+
if (!(m.getArguments().get(0) instanceof J.Literal)) {
190+
return null;
191+
}
192+
193+
J.Literal literal = (J.Literal) m.getArguments().get(0);
194+
String pluginId = "kotlin".equals(m.getSimpleName()) ? "org.jetbrains.kotlin." + literal.getValue() : (String) literal.getValue();
195+
return maybeGradlePlugin(cursor, pluginId, null, null, !withinBlock(cursor, "pluginManagement"));
196+
}
197+
} else if (isProjectReceiver(cursor) && APPLY_DSL_MATCHER.matches(m, true)) {
198+
Expression e = m.getArguments().get(0);
199+
if (e instanceof G.MapEntry) {
200+
G.MapEntry entry = (G.MapEntry) e;
201+
if (!(entry.getKey() instanceof J.Literal && "plugin".equals(((J.Literal) entry.getKey()).getValue()))) {
202+
return null;
203+
}
204+
205+
if (entry.getValue() instanceof J.Literal) {
206+
String pluginId = (String) ((J.Literal) entry.getValue()).getValue();
207+
return maybeGradlePlugin(cursor, pluginId, null, null, true);
208+
} else if (entry.getValue() instanceof J.FieldAccess || entry.getValue() instanceof J.Identifier) {
209+
return maybeGradlePlugin(cursor, null, null, null, true);
210+
}
211+
} else if (e instanceof J.Assignment) {
212+
J.Assignment assignment = (J.Assignment) e;
213+
if (!(assignment.getVariable() instanceof J.Identifier && ((J.Identifier) assignment.getVariable()).getSimpleName().equals("plugin")) ||
214+
!(assignment.getAssignment() instanceof J.Literal)) {
215+
return null;
216+
}
217+
218+
J.Literal literal = (J.Literal) assignment.getAssignment();
219+
String pluginId = (String) literal.getValue();
220+
return maybeGradlePlugin(cursor, pluginId, null, null, true);
221+
} else if (m.getTypeParameters() != null && !m.getTypeParameters().isEmpty()) {
222+
if (m.getTypeParameters().get(0) instanceof J.FieldAccess || m.getTypeParameters().get(0) instanceof J.Identifier) {
223+
return maybeGradlePlugin(cursor, null, null, null, true);
224+
}
225+
}
226+
}
227+
} else if (withinPlugins(cursor) && object instanceof J.Identifier) {
228+
J.Identifier i = (J.Identifier) object;
229+
String maybePluginId = i.getSimpleName();
230+
231+
if (!isCorePlugin(maybePluginId)) {
232+
return null;
233+
}
234+
235+
return maybeGradlePlugin(cursor, maybePluginId, null, null, true);
236+
}
237+
238+
return null;
239+
}
240+
241+
private boolean withinBlock(Cursor cursor, String name) {
242+
Cursor parentCursor = cursor.getParent();
243+
while (parentCursor != null) {
244+
if (parentCursor.getValue() instanceof J.MethodInvocation) {
245+
J.MethodInvocation m = parentCursor.getValue();
246+
if (m.getSimpleName().equals(name)) {
247+
return true;
248+
}
249+
}
250+
parentCursor = parentCursor.getParent();
251+
}
252+
253+
return false;
254+
}
255+
256+
private boolean withinPlugins(Cursor cursor) {
257+
Cursor parent = cursor.dropParentUntil(value -> value instanceof J.MethodInvocation || value == Cursor.ROOT_VALUE);
258+
if (parent.isRoot() || !((J.MethodInvocation) parent.getValue()).getSimpleName().equals("plugins")) {
259+
return false;
260+
}
261+
262+
parent = parent.dropParentUntil(value -> value instanceof J.MethodInvocation || value == Cursor.ROOT_VALUE);
263+
return parent.isRoot() || ((J.MethodInvocation) parent.getValue()).getSimpleName().equals("pluginManagement");
264+
}
265+
266+
private boolean isProjectReceiver(Cursor cursor) {
267+
Cursor parent = cursor.dropParentUntil(value -> value instanceof J.MethodInvocation || value == Cursor.ROOT_VALUE);
268+
if (parent.isRoot()) {
269+
return true;
270+
}
271+
272+
J.MethodInvocation m = parent.getValue();
273+
switch (m.getSimpleName()) {
274+
case "allprojects":
275+
case "subprojects":
276+
case "configure":
277+
return true;
278+
default:
279+
return false;
280+
}
281+
}
282+
283+
private boolean isCorePlugin(String pluginId) {
284+
for (String pluginName : CORE_PLUGIN_NAMES) {
285+
if (pluginName.equals(pluginId)) {
286+
return true;
287+
}
288+
}
289+
return false;
290+
}
291+
292+
private @Nullable GradlePlugin maybeGradlePlugin(Cursor cursor, @Nullable String pluginId, @Nullable String pluginClass, @Nullable String version, boolean applied) {
293+
if (!StringUtils.isBlank(pluginIdPattern) && !matchesGlob(pluginId, pluginIdPattern) ||
294+
!StringUtils.isBlank(this.pluginClass) && !matchesGlob(pluginClass, this.pluginClass)) {
295+
return null;
296+
}
297+
298+
return new GradlePlugin(cursor, pluginId, pluginClass, version, applied);
299+
}
300+
}
301+
}

0 commit comments

Comments
 (0)