Allow to specify which methods returning AutoCloseable will be ignored when called (IDEA-CR-7624)

This commit is contained in:
Bas Leijdekkers
2016-01-28 18:00:23 +01:00
parent d1f89dde8c
commit 0f1adf0d7c
5 changed files with 135 additions and 27 deletions
@@ -2030,7 +2030,8 @@ package.info.without.package.quickfix=add ''package {0};''
package.info.without.package.family.quickfix=add package statement
auto.closeable.resource.display.name=AutoCloseable used without 'try'-with-resources
auto.closeable.resource.problem.descriptor=''{0}'' used without ''try''-with-resources statement #loc
auto.closeable.resource.returned.option=Ignore AutoCloseable instances returned from method calls
auto.closeable.resource.quickfix=Ignore 'AutoCloseable' returned by this method
auto.closeable.resource.returned.option=Ignore AutoCloseable instances returned from all method calls
problematic.varargs.method.display.name=Non-varargs method overrides varargs method
problematic.varargs.method.override.problem.descriptor=Non-varargs method <code>#ref()</code> overrides varargs method #loc
negatively.named.boolean.variable.problem.descriptor=Boolean variable <code>#ref</code> is negatively named #loc
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* 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.
@@ -60,6 +60,44 @@ public class MethodMatcher {
return this;
}
public void add(@NotNull PsiMethodCallExpression expression) {
final PsiMethod method = expression.resolveMethod();
if (method != null) {
add(method);
}
}
public void add(@NotNull PsiMethod method) {
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return;
}
final String fqName = aClass.getQualifiedName();
final int index = myClassNames.indexOf(fqName);
final String methodName = method.getName();
if (index < 0) {
myClassNames.add(fqName);
myMethodNamePatterns.add(methodName);
}
else {
final String pattern = myMethodNamePatterns.get(index);
if (pattern.isEmpty()) {
myMethodNamePatterns.set(index, methodName);
return;
}
else if (".*".equals(pattern)) {
return;
}
final String[] names = pattern.split("\\|");
for (String name : names) {
if (methodName.equals(name)) {
return;
}
}
myMethodNamePatterns.set(index, pattern + '|' + methodName);
}
}
@NotNull
protected String getOptionName() {
return myOptionName;
@@ -15,18 +15,23 @@
*/
package com.siyeh.ig.resources;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.psiutils.MethodCallUtils;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.psiutils.MethodMatcher;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jdom.Element;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -43,6 +48,16 @@ public class AutoCloseableResourceInspectionBase extends ResourceInspection {
public boolean ignoreFromMethodCall = false;
final List<String> ignoredTypes = new ArrayList<String>(DEFAULT_IGNORED_TYPES);
protected final MethodMatcher myMethodMatcher;
public AutoCloseableResourceInspectionBase() {
myMethodMatcher = new MethodMatcher()
.add("java.util.Formatter", "format")
.add("java.io.Writer", "append")
.add("com.google.common.base.Preconditions", "checkNotNull")
.add("org.hibernate.Session", "close")
.finishDefault();
}
@Nls
@NotNull
@@ -65,6 +80,43 @@ public class AutoCloseableResourceInspectionBase extends ResourceInspection {
return InspectionGadgetsBundle.message("auto.closeable.resource.problem.descriptor", text);
}
@Nullable
@Override
protected InspectionGadgetsFix buildFix(Object... infos) {
final boolean buildQuickfix = ((Boolean)infos[1]).booleanValue();
if (!buildQuickfix) {
return null;
}
return new AutoCloseableResourceFix();
}
private class AutoCloseableResourceFix extends InspectionGadgetsFix {
@Nls
@NotNull
@Override
public String getName() {
return InspectionGadgetsBundle.message("auto.closeable.resource.quickfix");
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return getName();
}
@Override
protected void doFix(Project project, ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (methodCallExpression == null) {
return;
}
myMethodMatcher.add(methodCallExpression);
}
}
@Override
public void readSettings(@NotNull Element node) throws InvalidDataException {
super.readSettings(node);
@@ -78,6 +130,7 @@ public class AutoCloseableResourceInspectionBase extends ResourceInspection {
}
}
}
myMethodMatcher.readSettings(node);
}
@Override
@@ -88,6 +141,7 @@ public class AutoCloseableResourceInspectionBase extends ResourceInspection {
final String ignoredTypesString = formatString(ignoredTypes);
node.addContent(new Element("option").setAttribute("name", "ignoredTypes").setAttribute("value", ignoredTypesString));
}
myMethodMatcher.writeSettings(node);
}
@Override
@@ -114,25 +168,16 @@ public class AutoCloseableResourceInspectionBase extends ResourceInspection {
if (!isNotSafelyClosedResource(expression)) {
return;
}
registerNewExpressionError(expression, expression.getType());
registerNewExpressionError(expression, expression.getType(), Boolean.FALSE);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
if (ignoreFromMethodCall) {
if (ignoreFromMethodCall || myMethodMatcher.matches(expression) || !isNotSafelyClosedResource(expression)) {
return;
}
if (MethodCallUtils.isCallToMethod(expression, "java.util.Formatter", null, "format", null) ||
MethodCallUtils.isCallToMethod(expression, "java.io.Writer", null, "append", null) ||
MethodCallUtils.isCallToMethod(expression, "com.google.common.base.Preconditions", null, "checkNotNull", null) ||
MethodCallUtils.isCallToMethod(expression, "org.hibernate.Session", null, "close")) {
return;
}
if (!isNotSafelyClosedResource(expression)) {
return;
}
registerMethodCallError(expression, expression.getType());
registerMethodCallError(expression, expression.getType(), Boolean.TRUE);
}
@Override
@@ -150,7 +195,7 @@ public class AutoCloseableResourceInspectionBase extends ResourceInspection {
return;
}
}
registerError(expression, type);
registerError(expression, type, Boolean.FALSE);
}
private boolean isNotSafelyClosedResource(PsiExpression expression) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* 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.
@@ -17,31 +17,53 @@ package com.siyeh.ig.resources;
import com.intellij.codeInspection.ui.ListTable;
import com.intellij.codeInspection.ui.ListWrappingTableModel;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.util.ui.CheckBox;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.ui.UiUtils;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.util.Arrays;
/**
* @author Bas Leijdekkers
*/
public class AutoCloseableResourceInspection extends AutoCloseableResourceInspectionBase {
@NotNull
@Override
public JComponent createOptionsPanel() {
final JComponent panel = new JPanel(new BorderLayout());
final JComponent panel = new JPanel(new VerticalLayout(2));
final ListTable table =
new ListTable(new ListWrappingTableModel(ignoredTypes, InspectionGadgetsBundle.message("ignored.autocloseable.types.column.label")));
final JPanel tablePanel =
UiUtils.createAddRemoveTreeClassChooserPanel(table, InspectionGadgetsBundle.message("choose.autocloseable.type.to.ignore.title"),
"java.lang.AutoCloseable");
final MultipleCheckboxOptionsPanel checkboxPanel = new MultipleCheckboxOptionsPanel(this);
checkboxPanel.addCheckbox(InspectionGadgetsBundle.message("auto.closeable.resource.returned.option"), "ignoreFromMethodCall");
checkboxPanel.addCheckbox(InspectionGadgetsBundle.message("any.method.may.close.resource.argument"), "anyMethodMayClose");
panel.add(tablePanel, BorderLayout.CENTER);
panel.add(checkboxPanel, BorderLayout.SOUTH);
final ListTable table2 = new ListTable(
new ListWrappingTableModel(Arrays.asList(myMethodMatcher.getClassNames(), myMethodMatcher.getMethodNamePatterns()),
InspectionGadgetsBundle.message("result.of.method.call.ignored.class.column.title"),
InspectionGadgetsBundle.message("method.name.regex"))) {
@Override
public void setEnabled(boolean enabled) {
// hack to display correctly on initial opening of
// inspection settings (otherwise it is always enabled)
super.setEnabled(enabled && !ignoreFromMethodCall);
}
};
final JPanel tablePanel2 = UiUtils.createAddRemoveTreeClassChooserPanel(table2, "Choose class");
final JPanel wrapperPanel = new JPanel(new BorderLayout());
wrapperPanel.setBorder(IdeBorderFactory.createTitledBorder("Ignore AutoCloseable instances returned from these methods", false));
wrapperPanel.add(tablePanel2);
panel.add(tablePanel);
panel.add(wrapperPanel);
final CheckBox checkBox =
new CheckBox(InspectionGadgetsBundle.message("auto.closeable.resource.returned.option"), this, "ignoreFromMethodCall");
checkBox.addChangeListener(e -> table2.setEnabled(!ignoreFromMethodCall));
panel.add(checkBox);
panel.add(new CheckBox(InspectionGadgetsBundle.message("any.method.may.close.resource.argument"), this, "anyMethodMayClose"));
return panel;
}
}
@@ -1,14 +1,16 @@
<html>
<body>
Reports <b>AutoCloseable</b> instances which are not used in a <b>try</b>-with-resources statement, also known as
Reports <b>AutoCloseable</b> instances which are not used in a try-with-resources statement, also known as
<i>Automatic Resource Management</i>.
This means that the open resource before/in <b>try</b>, close in <b>finally</b> style which was used before
<b>try</b>-with-resources was available is also reported.
try-with-resources was available is also reported.
This inspection is meant to replace all <i>opened but not safely closed</i> inspections when developing in Java 7 and higher.
<p>
Use the table below to specify which AutoCloseable subclasses should be ignored by this inspection.
Use the first table below to specify which AutoCloseable subclasses should be ignored by this inspection.
Specify AutoCloseable subclasses here which do not need to be closed.
<p>
Use the second table below to specify which methods returning AutoCloseable will be ignored when called.
<p>
Use the first checkbox below to ignore an AutoCloseable if it the result of a method call.
When enabled, the results of factory methods will also be ignored.
<p>