diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml index 8e49f97515d0..ff8b0ed678e7 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/META-INF/InspectionGadgets.xml @@ -328,6 +328,10 @@ + #ref() optional.contains.collection.display.name='Optional' contains array or collection optional.contains.collection.problem.descriptor='Optional' contains collection #ref -optional.contains.array.problem.descriptor='Optional' contains array #ref \ No newline at end of file +optional.contains.array.problem.descriptor='Optional' contains array #ref +optional.get.without.is.present.display.name='Optional.get()' without 'isPresent()' check +optional.get.without.is.present.problem.descriptor={0}.#ref() without ''isPresent()'' check \ No newline at end of file diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/OptionalGetWithoutIsPresentInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/OptionalGetWithoutIsPresentInspection.java new file mode 100644 index 000000000000..c3450ac9c44f --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/bugs/OptionalGetWithoutIsPresentInspection.java @@ -0,0 +1,125 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * 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.siyeh.ig.bugs; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.siyeh.InspectionGadgetsBundle; +import com.siyeh.ig.BaseInspection; +import com.siyeh.ig.BaseInspectionVisitor; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.siyeh.ig.psiutils.TypeUtils; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class OptionalGetWithoutIsPresentInspection extends BaseInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return InspectionGadgetsBundle.message("optional.get.without.is.present.display.name"); + } + + @NotNull + @Override + protected String buildErrorString(Object... infos) { + final PsiType type = (PsiType)infos[0]; + final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type); + return InspectionGadgetsBundle.message("optional.get.without.is.present.problem.descriptor", aClass.getName()); + } + + @Override + public BaseInspectionVisitor buildVisitor() { + return new OptionalGetWithoutIsPresentVisitor(); + } + + private static class OptionalGetWithoutIsPresentVisitor extends BaseInspectionVisitor { + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + super.visitMethodCallExpression(expression); + final PsiReferenceExpression methodExpression = expression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!"get".equals(name) && !"getAsDouble".equals(name) && !"getAsInt".equals(name) && !"getAsLong".equals(name)) { + return; + } + final PsiExpression qualifier = ParenthesesUtils.stripParentheses(methodExpression.getQualifierExpression()); + if (!(qualifier instanceof PsiReferenceExpression)) { + return; + } + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)qualifier; + final PsiType type = qualifier.getType(); + if (!TypeUtils.isOptional(type)) { + return; + } + if (isSurroundedByIsPresentGuard(referenceExpression)) { + return; + } + registerMethodCallError(expression, type); + } + } + + private static boolean isSurroundedByIsPresentGuard(PsiReferenceExpression referenceExpression) { + PsiElement element = referenceExpression; + while (true) { + final PsiIfStatement ifStatement = PsiTreeUtil.getParentOfType(element, PsiIfStatement.class, true, + PsiMember.class, PsiLambdaExpression.class); + if (ifStatement == null) { + return false; + } + final PsiExpression condition = ifStatement.getCondition(); + if (isIsPresentCheck(condition, referenceExpression)) { + return true; + } + element = ifStatement; + } + } + + private static boolean isIsPresentCheck(@Nullable PsiExpression expression, PsiReferenceExpression reference) { + expression = ParenthesesUtils.stripParentheses(expression); + if (expression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression; + final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression(); + final String name = methodExpression.getReferenceName(); + if (!"isPresent".equals(name)) { + return false; + } + final PsiExpression qualifier = methodExpression.getQualifierExpression(); + if (!(qualifier instanceof PsiReferenceExpression)) { + return false; + } + final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)qualifier; + final PsiElement qTarget = referenceExpression.resolve(); + final PsiElement target = reference.resolve(); + return qTarget != null && qTarget.equals(target); + } + else if (expression instanceof PsiPolyadicExpression) { + final PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)expression; + final PsiExpression[] operands = polyadicExpression.getOperands(); + for (PsiExpression operand : operands) { + if (isIsPresentCheck(operand, reference)) { + return true; + } + } + } + return false; + } +} diff --git a/plugins/InspectionGadgets/src/inspectionDescriptions/OptionalGetWithoutIsPresent.html b/plugins/InspectionGadgets/src/inspectionDescriptions/OptionalGetWithoutIsPresent.html new file mode 100644 index 000000000000..ea2a82e4d881 --- /dev/null +++ b/plugins/InspectionGadgets/src/inspectionDescriptions/OptionalGetWithoutIsPresent.html @@ -0,0 +1,9 @@ + + +Reports calls to java.util.Optional.get() without first checking with a isPresent() call if a value is available. +If the Optional does not contain a value, get() will throw an exception. + +

+New in 16 + + \ No newline at end of file diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/bugs/OptionalGetWithoutIsPresentInspectionTest.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/bugs/OptionalGetWithoutIsPresentInspectionTest.java new file mode 100644 index 000000000000..fe2e7b57082d --- /dev/null +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/bugs/OptionalGetWithoutIsPresentInspectionTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * 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.siyeh.ig.bugs; + +import com.intellij.codeInspection.InspectionProfileEntry; +import com.siyeh.ig.LightInspectionTestCase; +import junit.framework.TestCase; +import org.jetbrains.annotations.Nullable; + +/** + * @author Bas Leijdekkers + */ +public class OptionalGetWithoutIsPresentInspectionTest extends LightInspectionTestCase { + + public void testSimple() { + doTest("import java.util.Optional;" + + "class X {" + + " void a(Optional o) {" + + " System.out.println(o./*'Optional.get()' without 'isPresent()' check*/get/**/());" + + " }" + + "}"); + } + + public void testOptionalDouble() { + doTest("import java.util.OptionalDouble;" + + "class X {" + + " double a(OptionalDouble d) {" + + " return d./*'OptionalDouble.getAsDouble()' without 'isPresent()' check*/getAsDouble/**/();" + + " }" + + "}"); + } + + @Nullable + @Override + protected InspectionProfileEntry getInspection() { + return new OptionalGetWithoutIsPresentInspection(); + } + + @Override + protected String[] getEnvironmentClasses() { + return new String[] { + "package java.util;" + + "public final class Optional {" + + " public T get() {" + + " return null;" + + " }" + + " public boolean isPresent() {" + + " return true;" + + " }" + + "}", + + "package java.util;" + + "public final class OptionalDouble {" + + " public boolean isPresent() {" + + " return true;" + + " }" + + " public double getAsDouble() {" + + " return 0.0;" + + " }" + + "}" + }; + } +} \ No newline at end of file