Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@

import java.nio.file.Path;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

Expand Down Expand Up @@ -65,51 +67,35 @@ public class GradleParser implements Parser {
@Override
public Stream<SourceFile> parseInputs(Iterable<Input> sources, @Nullable Path relativeTo, ExecutionContext ctx) {
if (groovyBuildParser == null) {
Collection<Path> buildscriptClasspath = base.buildscriptClasspath;
if (buildscriptClasspath == null) {
buildscriptClasspath = defaultClasspath(ctx);
}
groovyBuildParser = GroovyParser.builder(base.groovyParser)
.classpath(buildscriptClasspath)
.classpath(mergeClasspath(base.buildscriptClasspath, ctx))
.compilerCustomizers(
new DefaultImportsCustomizer(),
config -> config.setScriptBaseClass("RewriteGradleProject")
)
.build();
}
if (kotlinBuildParser == null) {
Collection<Path> buildscriptClasspath = base.buildscriptClasspath;
if (buildscriptClasspath == null) {
buildscriptClasspath = defaultClasspath(ctx);
}
kotlinBuildParser = KotlinParser.builder(base.kotlinParser)
.classpath(buildscriptClasspath)
.classpath(mergeClasspath(base.buildscriptClasspath, ctx))
.dependsOn(KTS_BUILD_STUBS)
.isKotlinScript(true)
.scriptImplicitReceivers("org.gradle.api.Project")
.scriptDefaultImports(DefaultImportsCustomizer.DEFAULT_IMPORTS)
.build();
}
if (groovySettingsParser == null) {
Collection<Path> settingsClasspath = base.settingsClasspath;
if (settingsClasspath == null) {
settingsClasspath = defaultClasspath(ctx);
}
groovySettingsParser = GroovyParser.builder(base.groovyParser)
.classpath(settingsClasspath)
.classpath(mergeClasspath(base.settingsClasspath, ctx))
.compilerCustomizers(
new DefaultImportsCustomizer(),
config -> config.setScriptBaseClass("RewriteSettings")
)
.build();
}
if (kotlinSettingsParser == null) {
Collection<Path> settingsClasspath = base.settingsClasspath;
if (settingsClasspath == null) {
settingsClasspath = defaultClasspath(ctx);
}
kotlinSettingsParser = KotlinParser.builder(base.kotlinParser)
.classpath(settingsClasspath)
.classpath(mergeClasspath(base.settingsClasspath, ctx))
.dependsOn(KTS_SETTINGS_STUBS)
.isKotlinScript(true)
.scriptImplicitReceivers("org.gradle.api.initialization.Settings")
Expand Down Expand Up @@ -210,6 +196,21 @@ public String getDslName() {
}
}

/**
* Always include the default Gradle API stubs alongside any externally-provided classpath.
* The external classpath (e.g. settings buildscript dependencies) typically contains custom
* plugin jars but not the Gradle API itself, which is needed for correct type attribution
* of DSL methods like pluginManagement(), repositories(), gradlePluginPortal(), etc.
*/
private Collection<Path> mergeClasspath(@Nullable Collection<Path> provided, ExecutionContext ctx) {
if (provided == null) {
return defaultClasspath(ctx);
}
Set<Path> merged = new LinkedHashSet<>(defaultClasspath(ctx));
merged.addAll(provided);
return merged;
}

private List<Path> defaultClasspath(ExecutionContext ctx) {
if (defaultClasspath == null) {
defaultClasspath = JavaParser.dependenciesFromResources(ctx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.openrewrite.test.RewriteTest;
import org.openrewrite.tree.ParseError;

import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
Expand Down Expand Up @@ -571,6 +573,52 @@ void escapedBackslashesAndInterpolationInGString(@Language("groovy") String groo
assertThat(sourceFile).isNotInstanceOf(ParseError.class);
}

@Test
void settingsKtsTypeAttributionWithEmptyClasspath() {
var parser = new GradleParser(GradleParser.builder().settingsClasspath(Collections.emptyList()));
Stream<SourceFile> sourceFileStream = parser.parseInputs(
List.of(Parser.Input.fromString(Paths.get("settings.gradle.kts"),
"pluginManagement {\n repositories {\n gradlePluginPortal()\n }\n}\n")),
null, new InMemoryExecutionContext());
Optional<SourceFile> sf = sourceFileStream.findFirst();
assertThat(sf).isPresent();
new org.openrewrite.TreeVisitor<org.openrewrite.Tree, Integer>() {
@Override
public org.openrewrite.Tree visit(org.openrewrite.Tree tree, Integer p) {
if (tree instanceof J.MethodInvocation mi && "pluginManagement".equals(mi.getSimpleName())) {
assertThat(mi.getMethodType()).isNotNull();
assertThat(mi.getMethodType().getDeclaringType().getFullyQualifiedName())
.as("pluginManagement() should resolve to Settings")
.isEqualTo("org.gradle.api.initialization.Settings");
}
return super.visit(tree, p);
}
}.visit(sf.get(), 0);
}

@Test
void buildKtsTypeAttributionWithEmptyClasspath() {
var parser = new GradleParser(GradleParser.builder().buildscriptClasspath(Collections.emptyList()));
Stream<SourceFile> sourceFileStream = parser.parseInputs(
List.of(Parser.Input.fromString(Paths.get("build.gradle.kts"),
"repositories {\n mavenCentral()\n}\n")),
null, new InMemoryExecutionContext());
Optional<SourceFile> sf = sourceFileStream.findFirst();
assertThat(sf).isPresent();
new org.openrewrite.TreeVisitor<org.openrewrite.Tree, Integer>() {
@Override
public org.openrewrite.Tree visit(org.openrewrite.Tree tree, Integer p) {
if (tree instanceof J.MethodInvocation mi && "repositories".equals(mi.getSimpleName())) {
assertThat(mi.getMethodType()).isNotNull();
assertThat(mi.getMethodType().getDeclaringType().getFullyQualifiedName())
.as("repositories() should resolve to Project")
.isEqualTo("org.gradle.api.Project");
}
return super.visit(tree, p);
}
}.visit(sf.get(), 0);
}

/**
* Produces a stream of test expressions like `def a = "\\${System.getProperty('user.name')}"`
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,19 @@
import org.junit.jupiter.api.Test;
import org.openrewrite.DocumentExample;
import org.openrewrite.SourceFile;
import org.openrewrite.gradle.GradleParser;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaType;
import org.openrewrite.kotlin.KotlinParser;
import org.openrewrite.kotlin.tree.K;
import org.openrewrite.test.RewriteTest;
import org.openrewrite.test.SourceSpec;
import org.openrewrite.test.SourceSpecs;
import org.openrewrite.test.TypeValidation;

import java.nio.file.Paths;
import java.util.Collections;

import static org.openrewrite.gradle.Assertions.settingsGradle;
import static org.openrewrite.gradle.Assertions.settingsGradleKts;
Expand Down Expand Up @@ -682,4 +691,35 @@ void addToExistingPluginManagementWithPluginsBlockKts() {
)
);
}

/**
* Helper to create settingsGradleKts specs using a GradleParser with empty settingsClasspath.
* When settingsClasspath is explicitly set to empty (e.g. no custom plugins in the settings
* buildscript), the Gradle API stubs must still be included for correct type attribution.
*/
private static SourceSpecs settingsGradleKtsWithEmptyClasspath(String before) {
GradleParser.Builder parser = GradleParser.builder()
.kotlinParser(KotlinParser.builder().logCompilationWarningsAndErrors(true))
.settingsClasspath(Collections.emptyList());
SourceSpec<K.CompilationUnit> gradle = new SourceSpec<>(K.CompilationUnit.class, "gradle", parser, before, null);
gradle.path(Paths.get("settings.gradle.kts"));
return gradle;
}

@Test
void skipWhenExistsGradlePluginPortalKtsWithEmptyClasspath() {
rewriteRun(
spec -> spec.recipe(new AddSettingsPluginRepository("gradlePluginPortal", null))
.typeValidationOptions(TypeValidation.builder().methodInvocations(false).build()),
settingsGradleKtsWithEmptyClasspath(
"""
pluginManagement {
repositories {
gradlePluginPortal()
}
}
"""
)
);
}
}