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 @@ -2647,6 +2647,29 @@ class Test {
);
}

@Test
void appliesToJavaFilesWhenEcosystemIsJvm() {
// A Java source file is in the "jvm" ecosystem, so a jvm-scoped change still applies.
rewriteRun(
spec -> spec.recipe(new ChangeType("java.lang.Integer", "java.lang.Long", true, "jvm")),
java(
"public class ThinkPositive { private Integer fred = 1;}",
"public class ThinkPositive { private Long fred = 1;}"
)
);
}

@Test
void skipsJavaFilesWhenEcosystemIsNonJvm() {
// Scoping to a non-JVM ecosystem must skip JVM (Java) source files entirely.
rewriteRun(
spec -> spec.recipe(new ChangeType("java.lang.Integer", "java.lang.Long", true, "python")),
java(
"public class ThinkPositive { private Integer fred = 1;}"
)
);
}

@Test
void changeTypeAddsExplicitImportWhenStarImportsWouldBeAmbiguous() {
rewriteRun(
Expand Down
56 changes: 56 additions & 0 deletions rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.openrewrite.java;

import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.jspecify.annotations.Nullable;
Expand Down Expand Up @@ -56,6 +57,18 @@ public class ChangeType extends Recipe {
@Nullable
Boolean ignoreDefinition;

@Option(displayName = "Ecosystem",
description = "Restrict the type change to source files of a given ecosystem: `jvm` (Java, Kotlin, " +
"Groovy), `python`, or `javascript`. Python and JavaScript reuse the Java LST model (their " +
"compilation units implement `JavaSourceFile`), so without this filter a change targeting one " +
"ecosystem still scans the others' source files unnecessarily. Reference-based files (such as " +
"XML and properties) are always processed because they may carry type references. When `null` " +
"(the default), the change is applied to all source files.",
valid = {"jvm", "python", "javascript"},
required = false)
@Nullable
String ecosystem;

String displayName = "Change type";

@Override
Expand All @@ -82,6 +95,46 @@ public String getInstanceNameSuffix() {

String description = "Change a given type to another.";

public ChangeType(String oldFullyQualifiedTypeName, String newFullyQualifiedTypeName, @Nullable Boolean ignoreDefinition) {
this(oldFullyQualifiedTypeName, newFullyQualifiedTypeName, ignoreDefinition, null);
}

@JsonCreator
public ChangeType(String oldFullyQualifiedTypeName, String newFullyQualifiedTypeName, @Nullable Boolean ignoreDefinition, @Nullable String ecosystem) {
this.oldFullyQualifiedTypeName = oldFullyQualifiedTypeName;
this.newFullyQualifiedTypeName = newFullyQualifiedTypeName;
this.ignoreDefinition = ignoreDefinition;
this.ecosystem = ecosystem;
}

private boolean skipForEcosystem(JavaSourceFile cu) {
if (ecosystem == null || ecosystem.isEmpty()) {
return false;
}
String impl = cu.getClass().getName();
String fileEcosystem;
if (impl.startsWith("org.openrewrite.python.")) {
fileEcosystem = "python";
} else if (impl.startsWith("org.openrewrite.javascript.")) {
fileEcosystem = "javascript";
Comment thread
steve-aom-elliott marked this conversation as resolved.
} else {
// java, kotlin, groovy, scala, ...
fileEcosystem = "jvm";
}
return !normalizeEcosystem(ecosystem).equals(fileEcosystem);
}

private static String normalizeEcosystem(String ecosystem) {
switch (ecosystem.toLowerCase()) {
case "js":
return "javascript";
case "py":
return "python";
Comment on lines +129 to +132
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the alternatives also be in the valid set at that point?

default:
return ecosystem.toLowerCase();
}
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
TreeVisitor<?, ExecutionContext> condition = new TreeVisitor<Tree, ExecutionContext>() {
Expand All @@ -90,6 +143,9 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
stopAfterPreVisit();
if (tree instanceof JavaSourceFile) {
JavaSourceFile cu = (JavaSourceFile) tree;
if (skipForEcosystem(cu)) {
return tree;
}
if (!Boolean.TRUE.equals(ignoreDefinition) && containsClassDefinition(cu, oldFullyQualifiedTypeName)) {
return SearchResult.found(cu);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ void changeBuiltinType() {
);
}

@Test
void skippedWhenEcosystemIsJvm() {
// Python compilation units implement JavaSourceFile, so ChangeType would otherwise traverse them.
// Scoping the change to the "jvm" ecosystem must skip Python source files entirely (leaving them
// unchanged and avoiding the cost of scanning them).
rewriteRun(
spec -> spec
.typeValidationOptions(TypeValidation.none())
.recipe(new ChangeType("str", "String", false, "jvm")),
python(
"""
result = "hello".upper()
"""
// unchanged: skipped because this is a Python (non-JVM) source file
)
);
}

// Note: ChangeType on module references (like os.path -> pathlib) requires
// additional work to handle Python's module import structure properly.
// Currently ChangeType works on type attribution but may not work on
Expand Down