From e072bcc0dfb3c136d2138f6a4ef45c199d8f6071 Mon Sep 17 00:00:00 2001 From: Vincent Potucek Date: Sun, 24 Aug 2025 11:44:28 +0200 Subject: [PATCH] NoEmptyUUID --- .../staticanalysis/NoEmptyUUID.java | 209 ++++++++ .../staticanalysis/NoEmptyUUIDTest.java | 486 ++++++++++++++++++ 2 files changed, 695 insertions(+) create mode 100644 src/main/java/org/openrewrite/staticanalysis/NoEmptyUUID.java create mode 100644 src/test/java/org/openrewrite/staticanalysis/NoEmptyUUIDTest.java diff --git a/src/main/java/org/openrewrite/staticanalysis/NoEmptyUUID.java b/src/main/java/org/openrewrite/staticanalysis/NoEmptyUUID.java new file mode 100644 index 0000000000..c77c3cb429 --- /dev/null +++ b/src/main/java/org/openrewrite/staticanalysis/NoEmptyUUID.java @@ -0,0 +1,209 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.staticanalysis; + +import org.openrewrite.*; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.*; + +public class NoEmptyUUID extends Recipe { + + @Override + public String getDisplayName() { + return "Remove impossible UUID emptiness checks"; + } + + @Override + public String getDescription() { + return "UUID.toString() always returns a non-empty string, so isEmpty() and isBlank() always return false."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + private final MethodMatcher uuidToStringMatcher = new MethodMatcher("java.util.UUID toString()"); + private final MethodMatcher isEmptyMatcher = new MethodMatcher("java.lang.String isEmpty()"); + private final MethodMatcher isBlankMatcher = new MethodMatcher("java.lang.String isBlank()"); + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + // Skip processing if this is already a replaced literal (not a real method invocation) + if (method.getMethodType() == null) { + return method; + } + + J.MethodInvocation mi = super.visitMethodInvocation(method, ctx); + + // Check if this is a call to isEmpty() or isBlank() + if (isEmptyMatcher.matches(mi) || isBlankMatcher.matches(mi)) { + Expression select = mi.getSelect(); + + // Check if the select is a UUID.toString() call or chain + if (isUUIDToStringCall(select)) { + // Replace with false + return JavaTemplate.builder("false") + .contextSensitive() + .build() + .apply(getCursor(), mi.getCoordinates().replace()); + } + } + + return mi; + } + + private boolean isUUIDToStringCall(Expression expression) { + if (expression instanceof J.MethodInvocation) { + J.MethodInvocation mi = (J.MethodInvocation) expression; + + // Direct UUID.toString() call + if (uuidToStringMatcher.matches(mi)) { + return true; + } + + // Chained calls like UUID.toString().trim() + if (mi.getSelect() != null) { + return isUUIDToStringCall(mi.getSelect()); + } + } + + // Check if this is an identifier that references a UUID string + if (expression instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) expression; + return isUUIDStringVariable(identifier); + } + + return false; + } + + private boolean isUUIDStringVariable(J.Identifier identifier) { + // Look for variable declarations that come from UUID.toString() + Cursor parent = getCursor().dropParentUntil(is -> + is instanceof J.VariableDeclarations || is instanceof J.MethodInvocation); + + if (parent.getValue() instanceof J.VariableDeclarations) { + J.VariableDeclarations varDecl = (J.VariableDeclarations) parent.getValue(); + for (J.VariableDeclarations.NamedVariable variable : varDecl.getVariables()) { + if (variable.getName().getSimpleName().equals(identifier.getSimpleName())) { + Expression initializer = variable.getInitializer(); + return initializer != null && isUUIDToStringCall(initializer); + } + } + } + + return false; + } + + @Override + public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) { + J.Lambda l = super.visitLambda(lambda, ctx); + + if (l.getBody() instanceof J.MethodInvocation) { + J.MethodInvocation body = (J.MethodInvocation) l.getBody(); + if ((isEmptyMatcher.matches(body) || isBlankMatcher.matches(body)) && + isUUIDToStringCall(body.getSelect())) { + // Replace the lambda body with false + l = l.withBody( + JavaTemplate.builder("false") + .contextSensitive() + .build() + .apply(getCursor(), body.getCoordinates().replace()) + ); + } + } + + return l; + } + + @Override + public J.If visitIf(J.If iff, ExecutionContext ctx) { + J.If ifStatement = super.visitIf(iff, ctx); + + // Check if the condition contains a UUID emptiness check + if (ifStatement.getIfCondition() instanceof J.ControlParentheses) { + J.ControlParentheses controlParens = (J.ControlParentheses) ifStatement.getIfCondition(); + Expression condition = controlParens.getTree(); + + if (condition instanceof J.MethodInvocation) { + J.MethodInvocation conditionMi = (J.MethodInvocation) condition; + if ((isEmptyMatcher.matches(conditionMi) || isBlankMatcher.matches(conditionMi)) && + isUUIDToStringCall(conditionMi.getSelect())) { + // Remove the entire if statement since the condition is always false + return null; + } + } + } + + return ifStatement; + } + + @Override + public J.WhileLoop visitWhileLoop(J.WhileLoop whileLoop, ExecutionContext ctx) { + J.WhileLoop wl = super.visitWhileLoop(whileLoop, ctx); + + // Check if the condition contains a UUID emptiness check + if (wl.getCondition() instanceof J.ControlParentheses) { + J.ControlParentheses controlParens = (J.ControlParentheses) wl.getCondition(); + Expression condition = controlParens.getTree(); + + if (condition instanceof J.MethodInvocation) { + J.MethodInvocation conditionMi = (J.MethodInvocation) condition; + if ((isEmptyMatcher.matches(conditionMi) || isBlankMatcher.matches(conditionMi)) && + isUUIDToStringCall(conditionMi.getSelect())) { + // Replace with while (false) + return wl.withCondition( + JavaTemplate.builder("(false)") + .contextSensitive() + .build() + .apply(getCursor(), wl.getCondition().getCoordinates().replace()) + ); + } + } + } + + return wl; + } + + @Override + public J.DoWhileLoop visitDoWhileLoop(J.DoWhileLoop doWhileLoop, ExecutionContext ctx) { + J.DoWhileLoop dwl = super.visitDoWhileLoop(doWhileLoop, ctx); + + // Check if the condition contains a UUID emptiness check + if (dwl.getWhileCondition() instanceof J.ControlParentheses) { + J.ControlParentheses controlParens = (J.ControlParentheses) dwl.getWhileCondition(); + Expression condition = controlParens.getTree(); + + if (condition instanceof J.MethodInvocation) { + J.MethodInvocation conditionMi = (J.MethodInvocation) condition; + if ((isEmptyMatcher.matches(conditionMi) || isBlankMatcher.matches(conditionMi)) && + isUUIDToStringCall(conditionMi.getSelect())) { + // Replace with while (false) + return dwl.withWhileCondition( + JavaTemplate.builder("(false)") + .contextSensitive() + .build() + .apply(getCursor(), dwl.getWhileCondition().getCoordinates().replace()) + ); + } + } + } + + return dwl; + } + }; + } +} diff --git a/src/test/java/org/openrewrite/staticanalysis/NoEmptyUUIDTest.java b/src/test/java/org/openrewrite/staticanalysis/NoEmptyUUIDTest.java new file mode 100644 index 0000000000..654c97cd5a --- /dev/null +++ b/src/test/java/org/openrewrite/staticanalysis/NoEmptyUUIDTest.java @@ -0,0 +1,486 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.staticanalysis; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class NoEmptyUUIDTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new NoEmptyUUID()) + .parser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params")); + } + + @Test + void uuidIsBlankInIfCondition() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + if (id.toString().isBlank()) { + System.out.println("This will never happen"); + } + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + } + } + """ + ) + ); + } + + @DocumentExample + @Test + void uuidToStringIsBlankAlwaysFalse() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + boolean b = id.toString().isBlank(); // Always false + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + boolean b = false; // Always false + } + } + """ + ) + ); + } + + @Test + void nonsensicalIfConditionWithUUID() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + if (id.toString() != null && id.toString().isEmpty()) { // Always false + System.out.println("Dead code"); + } + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + if (id.toString() != null && false) { // Always false + System.out.println("Dead code"); + } + } + } + """ + ) + ); + } + + @Test + void complexNonsensicalCondition() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + if ((id.toString().length() > 0) && id.toString().isEmpty()) { // Always false + System.out.println("Impossible condition"); + } + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + if ((id.toString().length() > 0) && false) { // Always false + System.out.println("Impossible condition"); + } + } + } + """ + ) + ); + } + + @Test + void whileLoopWithImpossibleCondition() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + while (id.toString().isEmpty()) { // Always false, infinite loop won't execute + System.out.println("This will never run"); + } + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + while (false) { // Always false, infinite loop won't execute + System.out.println("This will never run"); + } + } + } + """ + ) + ); + } + + @Test + void doWhileWithImpossibleCondition() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + do { + System.out.println("This runs once but condition is always false"); + } while (id.toString().isEmpty()); // Always false + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + do { + System.out.println("This runs once but condition is always false"); + } while (false); // Always false + } + } + """ + ) + ); + } + + @Test + void uuidIsBlankWithTrimChaining() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + boolean b = UUID.randomUUID().toString().trim().isBlank(); // Always false + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + boolean b = false; // Always false + } + } + """ + ) + ); + } + + @Test + void uuidIsEmptyAfterSubstringOperation() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + boolean b = id.toString().substring(0, 5).isEmpty(); // Always false (substring of UUID won't be empty) + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + boolean b = false; // Always false (substring of UUID won't be empty) + } + } + """ + ) + ); + } + + @Test + void uuidIsEmptyInMethodChain() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + processUUID(UUID.randomUUID().toString().isEmpty()); // Always false + } + private void processUUID(boolean isEmpty) {} + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + processUUID(false); // Always false + } + private void processUUID(boolean isEmpty) {} + } + """ + ) + ); + } + + @Test + void uuidIsBlankInReturnStatement() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public boolean test() { + return UUID.randomUUID().toString().isBlank(); // Always returns false + } + } + """, + """ + import java.util.UUID; + public class Test { + public boolean test() { + return false; // Always returns false + } + } + """ + ) + ); + } + + @Test + void uuidIsEmptyInStreamOperation() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + import java.util.List; + public class Test { + public void test() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + boolean anyEmpty = uuids.stream() + .anyMatch(uuid -> uuid.toString().isEmpty()); // Always false + } + } + """, + """ + import java.util.UUID; + import java.util.List; + public class Test { + public void test() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + boolean anyEmpty = uuids.stream() + .anyMatch(uuid -> false); // Always false + } + } + """ + ) + ); + } + + @Test + void uuidIsBlankInStreamFilter() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + import java.util.List; + public class Test { + public void test() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + List emptyOnes = uuids.stream() + .filter(uuid -> uuid.toString().isBlank()) // Always empty list + .toList(); + } + } + """, + """ + import java.util.UUID; + import java.util.List; + public class Test { + public void test() { + List uuids = List.of(UUID.randomUUID(), UUID.randomUUID()); + List emptyOnes = uuids.stream() + .filter(uuid -> false) // Always empty list + .toList(); + } + } + """ + ) + ); + } + + @Test + void uuidIsEmptyInSwitchCaseExpression() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test(int value) { + switch (value) { + case 1 -> System.out.println(UUID.randomUUID().toString().isEmpty()); // Always false + default -> System.out.println("default"); + } + } + } + """, + """ + import java.util.UUID; + public class Test { + public void test(int value) { + switch (value) { + case 1 -> System.out.println(false); // Always false + default -> System.out.println("default"); + } + } + } + """ + ) + ); + } + + @Test + void uuidIsBlankInTernaryWithMethodCall() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + String result = id.toString().isBlank() ? getEmptyMessage() : getNonEmptyMessage(); // Always calls getNonEmptyMessage() + } + private String getEmptyMessage() { return "empty"; } + private String getNonEmptyMessage() { return "not empty"; } + } + """, + """ + import java.util.UUID; + public class Test { + public void test() { + UUID id = UUID.randomUUID(); + String result = false ? getEmptyMessage() : getNonEmptyMessage(); // Always calls getNonEmptyMessage() + } + private String getEmptyMessage() { return "empty"; } + private String getNonEmptyMessage() { return "not empty"; } + } + """ + ) + ); + } + + @Test + void skipValidStringIsEmptyChecks() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + String potentiallyEmpty = getStringFromSomewhere(); + boolean validCheck = potentiallyEmpty.isEmpty(); // OK - not a UUID + + UUID id = UUID.randomUUID(); + String uuidString = id.toString(); + boolean alsoValid = uuidString.isEmpty(); // OK - variable is String type + } + private String getStringFromSomewhere() { return ""; } + } + """ + ) + ); + } + + @Test + void skipValidStringIsBlankChecks() { + rewriteRun( + //language=java + java( + """ + import java.util.UUID; + public class Test { + public void test() { + String potentiallyBlank = getStringFromSomewhere(); + boolean validCheck = potentiallyBlank.isBlank(); // OK - not a UUID + } + private String getStringFromSomewhere() { return " "; } + } + """ + ) + ); + } +}