Skip to content

Commit 5e680f8

Browse files
authored
Add type attribution for Gradle plugin DSL in KTS files (#6804)
* Add type attribution for Gradle plugin DSL in KTS files Add Kotlin stubs to GradleParser to provide PluginDependenciesSpec type information for the plugins {} block in .gradle.kts files. This enables recipes to use MethodMatcher consistently for both Groovy and KTS. Removed isKotlin branching from RemovePluginVisitor and updated FindPlugins to use real Gradle API types instead of synthetic stubs. * Update UpgradePluginVersion and AddPluginVisitor to use MethodMatcher Use MethodMatcher for version detection in UpgradePluginVersion instead of string-based name matching. Tighten AddPluginVisitor's wildcard id matcher to use real PluginDependenciesSpec type. * Use MethodMatcher for plugins container matching Use wildcard MethodMatcher for plugins {} container detection instead of bare name checks. A wildcard type is necessary because KTS extension functions have a file-level declaring type rather than Project/Settings. * Strengthen check in RemovePluginVisitor for Groovy
1 parent 2fda680 commit 5e680f8

5 files changed

Lines changed: 59 additions & 49 deletions

File tree

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@
3636

3737
@RequiredArgsConstructor
3838
public class GradleParser implements Parser {
39+
@SuppressWarnings("LanguageMismatch")
40+
private static final String KTS_BUILD_STUBS =
41+
"package org.gradle.api\n" +
42+
"import org.gradle.plugin.use.PluginDependenciesSpec\n" +
43+
"import org.gradle.plugin.use.PluginDependencySpec\n" +
44+
"fun Project.plugins(block: PluginDependenciesSpec.() -> Unit) {}\n" +
45+
"fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec = id(module)\n";
46+
47+
@SuppressWarnings("LanguageMismatch")
48+
private static final String KTS_SETTINGS_STUBS =
49+
"package org.gradle.api.initialization\n" +
50+
"import org.gradle.plugin.use.PluginDependenciesSpec\n" +
51+
"import org.gradle.plugin.use.PluginDependencySpec\n" +
52+
"import org.gradle.plugin.management.PluginManagementSpec\n" +
53+
"fun Settings.plugins(block: PluginDependenciesSpec.() -> Unit) {}\n" +
54+
"fun Settings.pluginManagement(block: PluginManagementSpec.() -> Unit) {}\n" +
55+
"fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec = id(module)\n";
56+
3957
private final GradleParser.Builder base;
4058

4159
private @Nullable List<Path> defaultClasspath;
@@ -66,6 +84,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
6684
}
6785
kotlinBuildParser = KotlinParser.builder(base.kotlinParser)
6886
.classpath(buildscriptClasspath)
87+
.dependsOn(KTS_BUILD_STUBS)
6988
.isKotlinScript(true)
7089
.scriptImplicitReceivers("org.gradle.api.Project")
7190
.scriptDefaultImports(DefaultImportsCustomizer.DEFAULT_IMPORTS)
@@ -91,6 +110,7 @@ public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path re
91110
}
92111
kotlinSettingsParser = KotlinParser.builder(base.kotlinParser)
93112
.classpath(settingsClasspath)
113+
.dependsOn(KTS_SETTINGS_STUBS)
94114
.isKotlinScript(true)
95115
.scriptImplicitReceivers("org.gradle.api.initialization.Settings")
96116
.scriptDefaultImports(DefaultImportsCustomizer.DEFAULT_IMPORTS)

rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/AddPluginVisitor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,9 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integ
299299
}
300300

301301
private K.CompilationUnit addPluginToKotlinCompilationUnit(K.CompilationUnit cu, ExecutionContext ctx) {
302-
MethodMatcher pluginsMatcher = new MethodMatcher("*..* plugins(..)");
303-
MethodMatcher pluginIdMatcher = new MethodMatcher("*..* id(..)");
302+
// Wildcard type because KTS extension functions have a file-level declaring type, not Project/Settings
303+
MethodMatcher pluginsMatcher = new MethodMatcher("* plugins(..)", false);
304+
MethodMatcher pluginIdMatcher = new MethodMatcher("org.gradle.plugin.use.PluginDependenciesSpec id(..)", true);
304305
AtomicBoolean hasPlugin = new JavaIsoVisitor<AtomicBoolean>() {
305306
@Override
306307
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, AtomicBoolean found) {

rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/RemovePluginVisitor.java

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@
3232
public class RemovePluginVisitor extends JavaIsoVisitor<ExecutionContext> {
3333
String pluginId;
3434

35-
MethodMatcher buildPluginsContainerMatcher = new MethodMatcher("org.gradle.api.Project plugins(..)", true);
35+
// Wildcard type because KTS extension functions have a file-level declaring type, not Project/Settings
36+
MethodMatcher pluginsMatcher = new MethodMatcher("* plugins(..)", false);
3637
MethodMatcher applyPluginMatcher = new MethodMatcher("org.gradle.api.Project apply(..)", true);
38+
MethodMatcher buildPluginsContainerMatcher = new MethodMatcher("org.gradle.api.Project plugins(..)", true);
3739
MethodMatcher settingsPluginsContainerMatcher = new MethodMatcher("org.gradle.api.initialization.Settings plugins(..)", true);
3840

3941
MethodMatcher pluginIdMatcher = new MethodMatcher("org.gradle.plugin.use.PluginDependenciesSpec id(..)", true);
@@ -45,15 +47,7 @@ public J.Block visitBlock(J.Block block, ExecutionContext executionContext) {
4547
J.Block b = super.visitBlock(block, executionContext);
4648

4749
J.MethodInvocation enclosingMethod = getCursor().firstEnclosing(J.MethodInvocation.class);
48-
if (enclosingMethod == null) {
49-
return b;
50-
}
51-
52-
boolean isKotlin = getCursor().firstEnclosing(K.CompilationUnit.class) != null;
53-
boolean isPluginsBlock = isKotlin ?
54-
"plugins".equals(enclosingMethod.getSimpleName()) :
55-
buildPluginsContainerMatcher.matches(enclosingMethod) || settingsPluginsContainerMatcher.matches(enclosingMethod);
56-
if (!isPluginsBlock) {
50+
if (enclosingMethod == null || !isPluginsMethod(enclosingMethod)) {
5751
return b;
5852
}
5953

@@ -68,28 +62,28 @@ public J.Block visitBlock(J.Block block, ExecutionContext executionContext) {
6862
}
6963

7064
// Check for id("pluginId")
71-
if (isIdMethodInvocation(m, isKotlin)) {
65+
if (isIdMethodInvocation(m)) {
7266
if (isPluginLiteral(m.getArguments().get(0))) {
7367
return null;
7468
}
7569
}
7670
// Check for id("pluginId").version("...")
77-
else if (isVersionMethodInvocation(m, isKotlin)) {
71+
else if (isVersionMethodInvocation(m)) {
7872
if (m.getSelect() instanceof J.MethodInvocation &&
7973
isPluginLiteral(((J.MethodInvocation) m.getSelect()).getArguments().get(0))) {
8074
return null;
8175
}
8276
}
8377
// Check for id("pluginId").apply(...) or id("pluginId").version("...").apply(...)
84-
else if (isApplyMethodInvocation(m, isKotlin)) {
85-
if (isIdMethodInvocation(m.getSelect(), isKotlin)) {
78+
else if (isApplyMethodInvocation(m)) {
79+
if (isIdMethodInvocation(m.getSelect())) {
8680
if (m.getSelect() instanceof J.MethodInvocation &&
8781
isPluginLiteral(((J.MethodInvocation) m.getSelect()).getArguments().get(0))) {
8882
return null;
8983
}
90-
} else if (isVersionMethodInvocation(m.getSelect(), isKotlin)) {
84+
} else if (isVersionMethodInvocation(m.getSelect())) {
9185
if (m.getSelect() instanceof J.MethodInvocation &&
92-
isIdMethodInvocation(((J.MethodInvocation) m.getSelect()).getSelect(), isKotlin)) {
86+
isIdMethodInvocation(((J.MethodInvocation) m.getSelect()).getSelect())) {
9387
if (((J.MethodInvocation) m.getSelect()).getSelect() instanceof J.MethodInvocation &&
9488
isPluginLiteral(((J.MethodInvocation) ((J.MethodInvocation) m.getSelect()).getSelect()).getArguments().get(0))) {
9589
return null;
@@ -102,62 +96,53 @@ else if (isApplyMethodInvocation(m, isKotlin)) {
10296
}));
10397
}
10498

99+
private boolean isPluginsMethod(J.MethodInvocation m) {
100+
// Specifically for Kotlin type information is still missing; match strongly where possible for Groovy
101+
return getCursor().firstEnclosing(K.CompilationUnit.class) != null ?
102+
pluginsMatcher.matches(m) :
103+
buildPluginsContainerMatcher.matches(m, true) || settingsPluginsContainerMatcher.matches(m);
104+
}
105+
105106
private boolean isPluginLiteral(Expression expression) {
106107
return expression instanceof J.Literal &&
107108
pluginId.equals(((J.Literal) expression).getValue());
108109
}
109110

110-
private boolean isIdMethodInvocation(@Nullable Expression expr, boolean isKotlin) {
111+
private boolean isIdMethodInvocation(@Nullable Expression expr) {
111112
if (!(expr instanceof J.MethodInvocation)) {
112113
return false;
113114
}
114-
J.MethodInvocation m = (J.MethodInvocation) expr;
115-
return isKotlin ?
116-
"id".equals(m.getSimpleName()) :
117-
pluginIdMatcher.matches(m);
115+
return pluginIdMatcher.matches((J.MethodInvocation) expr, true);
118116
}
119117

120-
private boolean isVersionMethodInvocation(@Nullable Expression expr, boolean isKotlin) {
118+
private boolean isVersionMethodInvocation(@Nullable Expression expr) {
121119
if (!(expr instanceof J.MethodInvocation)) {
122120
return false;
123121
}
124-
J.MethodInvocation m = (J.MethodInvocation) expr;
125-
return isKotlin ?
126-
"version".equals(m.getSimpleName()) :
127-
pluginVersionMatcher.matches(m);
122+
return pluginVersionMatcher.matches((J.MethodInvocation) expr, true);
128123
}
129124

130-
private boolean isApplyMethodInvocation(@Nullable Expression expr, boolean isKotlin) {
125+
private boolean isApplyMethodInvocation(@Nullable Expression expr) {
131126
if (!(expr instanceof J.MethodInvocation)) {
132127
return false;
133128
}
134-
J.MethodInvocation m = (J.MethodInvocation) expr;
135-
return isKotlin ?
136-
"apply".equals(m.getSimpleName()) :
137-
pluginApplyMatcher.matches(m);
129+
return pluginApplyMatcher.matches((J.MethodInvocation) expr, true);
138130
}
139131

140132
@Override
141133
public J.@Nullable MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
142134
J.MethodInvocation m = super.visitMethodInvocation(method, executionContext);
143135

144-
boolean isKotlin = getCursor().firstEnclosing(K.CompilationUnit.class) != null;
145-
146136
// Check for empty plugins{} block
147-
boolean isPluginsMethod = isKotlin ?
148-
"plugins".equals(m.getSimpleName()) :
149-
(buildPluginsContainerMatcher.matches(m) || settingsPluginsContainerMatcher.matches(m));
150-
151-
if (isPluginsMethod) {
137+
if (isPluginsMethod(m)) {
152138
if (m.getArguments().get(0) instanceof J.Lambda &&
153139
((J.Lambda) m.getArguments().get(0)).getBody() instanceof J.Block &&
154140
((J.Block) ((J.Lambda) m.getArguments().get(0)).getBody()).getStatements().isEmpty()) {
155141
return null;
156142
}
157143
}
158144
// Check for TOP-LEVEL apply plugin: "..." or apply(plugin = "...")
159-
else if ((isKotlin && "apply".equals(m.getSimpleName())) ||
160-
(!isKotlin && applyPluginMatcher.matches(m))) {
145+
else if (applyPluginMatcher.matches(m, true)) {
161146
for (Expression arg : m.getArguments()) {
162147
if (arg instanceof G.MapEntry) {
163148
G.MapEntry me = (G.MapEntry) arg;

rewrite-gradle/src/main/java/org/openrewrite/gradle/plugins/UpgradePluginVersion.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.openrewrite.internal.ListUtils;
2929
import org.openrewrite.internal.StringUtils;
3030
import org.openrewrite.java.JavaVisitor;
31+
import org.openrewrite.java.MethodMatcher;
3132
import org.openrewrite.java.tree.Expression;
3233
import org.openrewrite.java.tree.J;
3334
import org.openrewrite.maven.MavenDownloadingException;
@@ -53,6 +54,7 @@
5354
@EqualsAndHashCode(callSuper = false)
5455
public class UpgradePluginVersion extends ScanningRecipe<UpgradePluginVersion.DependencyVersionState> {
5556
private static final String GRADLE_PROPERTIES_FILE_NAME = "gradle.properties";
57+
private static final MethodMatcher VERSION_MATCHER = new MethodMatcher("org.gradle.plugin.use.PluginDependencySpec version(..)", true);
5658

5759
@EqualsAndHashCode.Exclude
5860
transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this);
@@ -112,7 +114,7 @@ private boolean isPluginVersion(Cursor cursor) {
112114
return false;
113115
}
114116
J.MethodInvocation maybeVersion = cursor.getValue();
115-
if (!"version".equals(maybeVersion.getSimpleName())) {
117+
if (!VERSION_MATCHER.matches(maybeVersion, true)) {
116118
return false;
117119
}
118120
Cursor parent = cursor.dropParentUntil(it -> (it instanceof J.MethodInvocation) || it == Cursor.ROOT_VALUE);

rewrite-gradle/src/main/java/org/openrewrite/gradle/search/FindPlugins.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public Validated<Object> validate(ExecutionContext ctx) {
6868

6969
@Override
7070
public TreeVisitor<?, ExecutionContext> getVisitor() {
71-
MethodMatcher pluginMatcher = new MethodMatcher("PluginSpec id(..)", false);
71+
MethodMatcher pluginMatcher = new MethodMatcher("org.gradle.plugin.use.PluginDependenciesSpec id(..)", true);
7272

7373
return new TreeVisitor<Tree, ExecutionContext>() {
7474
@Override
@@ -85,7 +85,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
8585

8686
@Override
8787
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
88-
if (pluginMatcher.matches(method)) {
88+
if (pluginMatcher.matches(method, true)) {
8989
if (method.getArguments().get(0) instanceof J.Literal &&
9090
pluginId.equals(((J.Literal) method.getArguments().get(0)).getValue())) {
9191
found.set(true);
@@ -126,11 +126,13 @@ public static List<GradlePlugin> find(J j, String pluginIdPattern) {
126126
Function.identity()
127127
);
128128

129-
MethodMatcher idMatcher = new MethodMatcher("PluginSpec id(..)", false);
130-
MethodMatcher versionMatcher = new MethodMatcher("Plugin version(..)", false);
129+
MethodMatcher idMatcher = new MethodMatcher("org.gradle.plugin.use.PluginDependenciesSpec id(..)", true);
130+
MethodMatcher versionMatcher = new MethodMatcher("org.gradle.plugin.use.PluginDependencySpec version(..)", true);
131131
List<GradlePlugin> pluginsWithVersion = plugins.stream()
132132
.flatMap(plugin -> {
133-
if (versionMatcher.matches(plugin) && idMatcher.matches(plugin.getSelect()) && plugin.getArguments().get(0) instanceof J.Literal) {
133+
if (versionMatcher.matches(plugin, true) &&
134+
plugin.getSelect() instanceof J.MethodInvocation && idMatcher.matches((J.MethodInvocation) plugin.getSelect(), true) &&
135+
plugin.getArguments().get(0) instanceof J.Literal) {
134136
return Stream.of(new GradlePlugin(
135137
plugin,
136138
requireNonNull(((J.Literal) requireNonNull(((J.MethodInvocation) plugin.getSelect()))
@@ -141,7 +143,7 @@ public static List<GradlePlugin> find(J j, String pluginIdPattern) {
141143
return Stream.empty();
142144
}).collect(toList());
143145
List<GradlePlugin> pluginsWithoutVersion = plugins.stream().flatMap(plugin -> {
144-
if (idMatcher.matches(plugin) && pluginsWithVersion.stream()
146+
if (idMatcher.matches(plugin, true) && pluginsWithVersion.stream()
145147
.noneMatch(it -> it.getPluginId().equals(plugin.getSimpleName()))) {
146148
return Stream.of(new GradlePlugin(
147149
plugin,

0 commit comments

Comments
 (0)