diff --git a/README.md b/README.md index 3fb310332..c6b550611 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ Safe Logging can be found at [github.com/palantir/safe-logging](https://github.c - `BugCheckerAutoService`: Concrete BugChecker implementations should be annotated `@AutoService(BugChecker.class)` for auto registration with error-prone. - `DangerousCollapseKeysUsage`: Disallow usage of `EntryStream#collapseKeys()`. - `JooqBatchWithoutBindArgs`: jOOQ batch methods that execute without bind args can cause performance problems. +- `InvocationTargetExceptionGetTargetException`: InvocationTargetException.getTargetException() predates the general-purpose exception chaining facility. The Throwable.getCause() method is now the preferred means of obtaining this information. [(source)](https://docs.oracle.com/en/java/javase/17/docs/api//java.base/java/lang/reflect/InvocationTargetException.html#getTargetException()) ### Programmatic Application diff --git a/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetException.java b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetException.java new file mode 100644 index 000000000..2e033375d --- /dev/null +++ b/baseline-error-prone/src/main/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetException.java @@ -0,0 +1,56 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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 + * + * http://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 com.palantir.baseline.errorprone; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.fixes.SuggestedFixes; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.matchers.Matchers; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import java.lang.reflect.InvocationTargetException; + +@AutoService(BugChecker.class) +@BugPattern( + link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks", + linkType = BugPattern.LinkType.CUSTOM, + severity = SeverityLevel.SUGGESTION, + summary = + "InvocationTargetException.getTargetException() predates the general-purpose exception chaining" + + " facility. The Throwable.getCause() method is now the preferred means of obtaining this" + + " information. Source: " + + "https://docs.oracle.com/en/java/javase/17/docs/api//java.base/java/lang/reflect/InvocationTargetException.html#getTargetException()") +public final class InvocationTargetExceptionGetTargetException extends BugChecker + implements BugChecker.MethodInvocationTreeMatcher { + + private static final Matcher ITE_GET_TARGET_EXCEPTION_MATCHER = Matchers.instanceMethod() + .onDescendantOf(InvocationTargetException.class.getName()) + .named("getTargetException") + .withNoParameters(); + + @Override + public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { + return ITE_GET_TARGET_EXCEPTION_MATCHER.matches(tree, state) + ? describeMatch(tree, SuggestedFixes.renameMethodInvocation(tree, "getCause", state)) + : Description.NO_MATCH; + } +} diff --git a/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetExceptionTest.java b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetExceptionTest.java new file mode 100644 index 000000000..4bfb3f202 --- /dev/null +++ b/baseline-error-prone/src/test/java/com/palantir/baseline/errorprone/InvocationTargetExceptionGetTargetExceptionTest.java @@ -0,0 +1,70 @@ +/* + * (c) Copyright 2023 Palantir Technologies Inc. All rights reserved. + * + * 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 + * + * http://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 com.palantir.baseline.errorprone; + +import java.lang.reflect.InvocationTargetException; +import org.junit.jupiter.api.Test; + +public class InvocationTargetExceptionGetTargetExceptionTest { + @Test + public void test_basic() { + fix().addInputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " static Throwable f(InvocationTargetException foo) {", + " return foo.getTargetException();", + " }", + "}") + .addOutputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " static Throwable f(InvocationTargetException foo) {", + " return foo.getCause();", + " }", + "}") + .doTest(); + } + + @Test + public void test_subclass() { + fix().addInputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " class TestException extends InvocationTargetException {}", + " public Throwable getCause(TestException foo) {", + " return foo.getTargetException();", // This should change + " }", + "}") + .addOutputLines( + "Test.java", + "import " + InvocationTargetException.class.getName() + ";", + "class Test {", + " class TestException extends InvocationTargetException {}", + " public Throwable getCause(TestException foo) {", + " return foo.getCause();", + " }", + "}") + .doTest(); + } + + private RefactoringValidator fix() { + return RefactoringValidator.of(InvocationTargetExceptionGetTargetException.class, getClass()); + } +} diff --git a/changelog/@unreleased/pr-2541.v2.yml b/changelog/@unreleased/pr-2541.v2.yml new file mode 100644 index 000000000..28d640520 --- /dev/null +++ b/changelog/@unreleased/pr-2541.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: 'Add check: Use InvocationTargetException.getCause instead of getTargetException.' + links: + - https://github.com/palantir/gradle-baseline/pull/2541