diff --git a/src/main/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrows.java b/src/main/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrows.java index 53b978fce..d31a84a23 100755 --- a/src/main/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrows.java +++ b/src/main/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrows.java @@ -17,7 +17,9 @@ import org.openrewrite.ExecutionContext; import org.openrewrite.Recipe; +import org.openrewrite.SourceFile; import org.openrewrite.TreeVisitor; +import org.openrewrite.csharp.tree.Cs; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.Expression; @@ -113,6 +115,13 @@ private boolean onlyRethrows(J.Try.Catch aCatch) { } Expression exception = ((J.Throw) aCatch.getBody().getStatements().get(0)).getException(); + + // In C# an implicit rethrow is possible + if (getCursor().firstEnclosing(SourceFile.class) instanceof Cs && + exception instanceof J.Empty) { + return true; + } + JavaType catchParameterType = aCatch.getParameter().getType(); if (!(catchParameterType instanceof JavaType.MultiCatch)) { JavaType.FullyQualified catchType = TypeUtils.asFullyQualified(catchParameterType); diff --git a/src/test/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrowsTest.java b/src/test/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrowsTest.java index c6c93ebf1..f34d89657 100755 --- a/src/test/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrowsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/CatchClauseOnlyRethrowsTest.java @@ -17,10 +17,19 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.Tree; +import org.openrewrite.csharp.tree.Cs; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.Space; +import org.openrewrite.marker.Markers; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.test.RewriteTest.toRecipe; @SuppressWarnings("ALL") class CatchClauseOnlyRethrowsTest implements RewriteTest { @@ -38,7 +47,7 @@ void rethrownButWithDifferentMessage() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -63,7 +72,7 @@ void catchShouldBePreservedBecauseLessSpecificCatchFollows() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -90,7 +99,7 @@ void catchShouldBePreservedBecauseLessSpecificCatchFollowsWithMultiCast() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -116,7 +125,7 @@ void tryCanBeRemoved() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -130,7 +139,7 @@ void foo() throws IOException { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { new FileReader("").read(); @@ -150,7 +159,7 @@ void tryCanBeRemovedWithMultiCatch() { import java.io.FileReader; import java.io.IOException; import java.io.FileNotFoundException; - + class A { void foo() throws IOException { try { @@ -166,16 +175,16 @@ void foo() throws IOException { } """, """ - import java.io.FileReader; - import java.io.IOException; - import java.io.FileNotFoundException; - - class A { - void foo() throws IOException { - new FileReader("").read(); - } - } - """ + import java.io.FileReader; + import java.io.IOException; + import java.io.FileNotFoundException; + + class A { + void foo() throws IOException { + new FileReader("").read(); + } + } + """ ) ); } @@ -189,7 +198,7 @@ void multiCatchPreservedOnDifferentThrow() { import java.io.FileReader; import java.io.IOException; import java.io.FileNotFoundException; - + class A { void foo() throws IOException { try { @@ -214,7 +223,7 @@ void tryShouldBePreservedBecauseFinally() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -230,7 +239,7 @@ void foo() throws IOException { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -253,7 +262,7 @@ void tryShouldBePreservedBecauseResources() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try(FileReader fr = new FileReader("")) { @@ -267,7 +276,7 @@ void foo() throws IOException { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try(FileReader fr = new FileReader("")) { @@ -288,7 +297,7 @@ void wrappingAndRethrowingIsUnchanged() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() { try { @@ -311,7 +320,7 @@ void loggingAndRethrowingIsUnchanged() { """ import java.io.FileReader; import java.io.IOException; - + class A { void foo() throws IOException { try { @@ -326,4 +335,49 @@ void foo() throws IOException { ) ); } + + @Test + void verifyCsharpImplicitThrow() { + rewriteRun( + spec -> spec.recipe(toRecipe(() -> new JavaVisitor<>() { + @Override + public J visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) { + // C# can rethrow the caught exception implicitly and so the `e` Identifier is removed by the inline visitor below + Cs.CompilationUnit cscu = JavaToCsharp.compilationUnit(cu); + cscu = (Cs.CompilationUnit) new JavaVisitor() { + @Override + public J visitThrow(J.Throw thrown, ExecutionContext ctx) { + if (thrown.getException() instanceof J.Identifier) { + return thrown.withException(new J.Empty(Tree.randomId(), Space.EMPTY, Markers.EMPTY)); + } + return thrown; + } + }.visit(cscu, new InMemoryExecutionContext()); + // Exercise the regular recipe with the now modified CSharp compilation unit + return (J) new CatchClauseOnlyRethrows().getVisitor().visit(cscu, ctx); + } + })), + //language=java + java( + """ + class A { + void foo() throws IllegalAccessException { + try { + throw new IllegalAccessException(); + } catch (Exception e) { + throw e; // `e` is removed below + } + } + } + """, + """ + class A { + void foo() throws IllegalAccessException { + throw new IllegalAccessException(); + } + } + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/staticanalysis/JavaToCsharp.java b/src/test/java/org/openrewrite/staticanalysis/JavaToCsharp.java new file mode 100644 index 000000000..06b5b6221 --- /dev/null +++ b/src/test/java/org/openrewrite/staticanalysis/JavaToCsharp.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.csharp.tree.Cs; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JRightPadded; +import org.openrewrite.java.tree.Statement; + +import java.util.List; + +public class JavaToCsharp { + + public static Cs.CompilationUnit compilationUnit(J.CompilationUnit cu) { + return new Cs.CompilationUnit( + cu.getId(), + cu.getPrefix(), + cu.getMarkers(), + cu.getSourcePath(), + cu.getFileAttributes(), + cu.getCharset().name(), + cu.isCharsetBomMarked(), + cu.getChecksum(), + List.of(), + List.of(), + List.of(), + cu.getClasses().stream() + .map(Statement.class::cast) + .map(JRightPadded::build) + .toList(), + cu.getEof()); + } +}