IG; new "'Optional.get()' without 'isPresent()' check" inspection

This commit is contained in:
Bas Leijdekkers
2016-02-09 23:04:36 +01:00
parent d3e9a561a6
commit 7bed063ee1
5 changed files with 217 additions and 1 deletions
@@ -328,6 +328,10 @@
<localInspection groupPath="Java" language="JAVA" shortName="ObjectToString" bundle="com.siyeh.InspectionGadgetsBundle" key="default.tostring.call.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.probable.bugs" enabledByDefault="false" level="WARNING"
implementationClass="com.siyeh.ig.bugs.ObjectToStringInspection"/>
<localInspection groupPath="Java" language="JAVA" shortName="OptionalGetWithoutIsPresent" bundle="com.siyeh.InspectionGadgetsBundle"
key="optional.get.without.is.present.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.probable.bugs" enabledByDefault="true" level="WARNING"
implementationClass="com.siyeh.ig.bugs.OptionalGetWithoutIsPresentInspection"/>
<localInspection groupPath="Java" language="JAVA" suppressId="PrimitiveArrayArgumentToVarargsMethod" alternativeId="PrimitiveArrayArgumentToVariableArgMethod"
shortName="PrimitiveArrayArgumentToVariableArgMethod" bundle="com.siyeh.InspectionGadgetsBundle"
key="primitive.array.argument.to.var.arg.method.display.name" groupBundle="messages.InspectionsBundle"
@@ -2168,4 +2168,6 @@ lambda.unfriendly.method.overload.display.name=Lambda unfriendly method overload
lambda.unfriendly.method.overload.problem.descriptor=Lambda unfriendly overload of method <code>#ref()</code>
optional.contains.collection.display.name='Optional' contains array or collection
optional.contains.collection.problem.descriptor='Optional' contains collection <code>#ref</code>
optional.contains.array.problem.descriptor='Optional' contains array <code>#ref</code>
optional.contains.array.problem.descriptor='Optional' contains array <code>#ref</code>
optional.get.without.is.present.display.name='Optional.get()' without 'isPresent()' check
optional.get.without.is.present.problem.descriptor=<code>{0}.#ref()</code> without ''isPresent()'' check
@@ -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;
}
}
@@ -0,0 +1,9 @@
<html>
<body>
Reports calls to <b>java.util.Optional.get()</b> without first checking with a <b>isPresent()</b> call if a value is available.
If the <b>Optional</b> does not contain a value, <b>get()</b> will throw an exception.
<!-- tooltip end -->
<p>
<small>New in 16</small>
</body>
</html>
@@ -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<String> 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<T> {" +
" 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;" +
" }" +
"}"
};
}
}