diff --git a/framework/codemodder-base/src/main/java/io/codemodder/CodemodLoader.java b/framework/codemodder-base/src/main/java/io/codemodder/CodemodLoader.java index dbc6daf11..44ce46684 100644 --- a/framework/codemodder-base/src/main/java/io/codemodder/CodemodLoader.java +++ b/framework/codemodder-base/src/main/java/io/codemodder/CodemodLoader.java @@ -43,13 +43,26 @@ public CodemodLoader( // sort the codemods according to their priority List> orderedCodemodTypes = new ArrayList<>(unorderedCodemodTypes); - // sort according to the codemod execution priority of each codemod type - orderedCodemodTypes.sort( - (c1, c2) -> { - CodemodExecutionPriority p1 = c1.getAnnotation(Codemod.class).executionPriority(); - CodemodExecutionPriority p2 = c2.getAnnotation(Codemod.class).executionPriority(); - return CodemodExecutionPriority.priorityOrderComparator.compare(p1, p2); - }); + // if there's an order from --codemod-includes, honor that + Optional> desiredOrder = codemodRegulator.desiredCodemodIdOrder(); + if (desiredOrder.isPresent()) { + orderedCodemodTypes.sort( + (c1, c2) -> { + String id1 = c1.getAnnotation(Codemod.class).id(); + String id2 = c2.getAnnotation(Codemod.class).id(); + int index1 = desiredOrder.get().indexOf(id1); + int index2 = desiredOrder.get().indexOf(id2); + return Integer.compare(index1, index2); + }); + } else { + // sort according to the codemod execution priority of each codemod type + orderedCodemodTypes.sort( + (c1, c2) -> { + CodemodExecutionPriority p1 = c1.getAnnotation(Codemod.class).executionPriority(); + CodemodExecutionPriority p2 = c2.getAnnotation(Codemod.class).executionPriority(); + return CodemodExecutionPriority.priorityOrderComparator.compare(p1, p2); + }); + } // get all the injectable parameters Set packagesScanned = new HashSet<>(); @@ -127,7 +140,6 @@ public CodemodLoader( codemods.add(new CodemodIdPair(codemodId, codeChanger)); } } - this.codemods = Collections.unmodifiableList(codemods); } diff --git a/framework/codemodder-base/src/main/java/io/codemodder/CodemodRegulator.java b/framework/codemodder-base/src/main/java/io/codemodder/CodemodRegulator.java index a7f64c7ba..f642a527a 100644 --- a/framework/codemodder-base/src/main/java/io/codemodder/CodemodRegulator.java +++ b/framework/codemodder-base/src/main/java/io/codemodder/CodemodRegulator.java @@ -2,6 +2,7 @@ import java.util.List; import java.util.Objects; +import java.util.Optional; /** A type that is relied on to inform our in-flight analysis on whether codemods are allowed. */ public interface CodemodRegulator { @@ -14,15 +15,23 @@ public interface CodemodRegulator { */ boolean isAllowed(String codemodId); + Optional> desiredCodemodIdOrder(); + class DefaultCodemodRegulator implements CodemodRegulator { private final DefaultRuleSetting setting; private final List exceptions; + private final List desiredOrder; DefaultCodemodRegulator( final DefaultRuleSetting defaultCodemodSetting, final List codemodExceptions) { this.setting = Objects.requireNonNull(defaultCodemodSetting); this.exceptions = Objects.requireNonNull(codemodExceptions); + if (DefaultRuleSetting.ENABLED.equals(defaultCodemodSetting)) { + this.desiredOrder = null; + } else { + this.desiredOrder = codemodExceptions; + } } @Override @@ -32,6 +41,11 @@ public boolean isAllowed(final String codemodId) { } return exceptions.contains(codemodId); } + + @Override + public Optional> desiredCodemodIdOrder() { + return Optional.ofNullable(desiredOrder); + } } static CodemodRegulator of( diff --git a/framework/codemodder-base/src/test/java/io/codemodder/CodemodLoaderTest.java b/framework/codemodder-base/src/test/java/io/codemodder/CodemodLoaderTest.java index cea92f74c..5ee1ed817 100644 --- a/framework/codemodder-base/src/test/java/io/codemodder/CodemodLoaderTest.java +++ b/framework/codemodder-base/src/test/java/io/codemodder/CodemodLoaderTest.java @@ -3,6 +3,7 @@ import static io.codemodder.CodemodLoader.isValidCodemodId; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; import static org.junit.jupiter.api.Assertions.assertThrows; import com.github.javaparser.JavaParser; @@ -216,6 +217,81 @@ public boolean supports(Path file) { } } + /** Confirm the codemods respect the order of the regulator. */ + @Test + void it_handles_codemod_orders(final @TempDir Path tmpDir) throws IOException { + + // create an ordered list of codemods + List> codemodTypes = + List.of( + // test:java/changes-file + ChangesFile.class, + + // test:java/changes-file-again + ChangesFileAgain.class, + + // test:java/changes-file-yet-again + ChangesFileYetAgain.class); + + CodemodLoader loader = createLoader(codemodTypes, tmpDir); + List codemods = loader.getCodemods(); + assertThat(codemods.get(0).getId(), equalTo("test:java/changes-file")); + assertThat(codemods.get(1).getId(), equalTo("test:java/changes-file-again")); + assertThat(codemods.get(2).getId(), equalTo("test:java/changes-file-yet-again")); + + // now we specify a --codemod-includes order to be in backwards order + CodemodRegulator regulator = + CodemodRegulator.of( + DefaultRuleSetting.DISABLED, + List.of( + "test:java/changes-file-yet-again", + "test:java/changes-file-again", + "test:java/changes-file")); + loader = + new CodemodLoader( + codemodTypes, + regulator, + tmpDir, + List.of("**"), + List.of(), + Files.list(tmpDir).toList(), + Map.of(), + List.of(), + List.of(), + null, + null, + null); + + codemods = loader.getCodemods(); + assertThat(codemods.get(0).getId(), equalTo("test:java/changes-file-yet-again")); + assertThat(codemods.get(1).getId(), equalTo("test:java/changes-file-again")); + assertThat(codemods.get(2).getId(), equalTo("test:java/changes-file")); + + // now do it again, with only the B and C codemods + regulator = + CodemodRegulator.of( + DefaultRuleSetting.ENABLED, + List.of("test:java/changes-file-again", "test:java/changes-file")); + loader = + new CodemodLoader( + codemodTypes, + regulator, + tmpDir, + List.of("**"), + List.of(), + Files.list(tmpDir).toList(), + Map.of(), + List.of(), + List.of(), + null, + null, + null); + + codemods = loader.getCodemods(); + assertThat(codemods, hasSize(1)); + assertThat(codemods.get(0).getId(), equalTo("test:java/changes-file-yet-again")); + } + /** * We create a file, and then run two codemods on it. The first codemod adds a line, the second * adds another. This will ensure that the codemods are run in the order they are provided and diff --git a/framework/codemodder-base/src/test/java/io/codemodder/CodemodRegulatorTest.java b/framework/codemodder-base/src/test/java/io/codemodder/CodemodRegulatorTest.java new file mode 100644 index 000000000..40ef902f1 --- /dev/null +++ b/framework/codemodder-base/src/test/java/io/codemodder/CodemodRegulatorTest.java @@ -0,0 +1,28 @@ +package io.codemodder; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +final class CodemodRegulatorTest { + + @Test + void it_respects_order_on_includes() { + CodemodRegulator regulator = + CodemodRegulator.of(DefaultRuleSetting.DISABLED, List.of("c", "a", "b")); + Optional> desiredCodemodIdOrder = regulator.desiredCodemodIdOrder(); + assertThat(desiredCodemodIdOrder).isPresent(); + List includeOrder = desiredCodemodIdOrder.get(); + assertThat(includeOrder).containsExactly("c", "a", "b"); + } + + @Test + void it_doesnt_opine_on_order_when_excludes() { + CodemodRegulator regulator = + CodemodRegulator.of(DefaultRuleSetting.ENABLED, List.of("c", "a", "b")); + Optional> desiredCodemodIdOrder = regulator.desiredCodemodIdOrder(); + assertThat(desiredCodemodIdOrder).isEmpty(); + } +}